commit
e06dd199a7
24 changed files with 641 additions and 21 deletions
|
@ -346,6 +346,9 @@ desktop:
|
||||||
failed: "Failed to setup. please ensure that the token is correct."
|
failed: "Failed to setup. please ensure that the token is correct."
|
||||||
info: "From the next sign in, enter the token that is displayed on the device in addition to the password."
|
info: "From the next sign in, enter the token that is displayed on the device in addition to the password."
|
||||||
|
|
||||||
|
mk-mute-setting:
|
||||||
|
no-users: "No muted users"
|
||||||
|
|
||||||
mk-post-form:
|
mk-post-form:
|
||||||
post-placeholder: "What's happening?"
|
post-placeholder: "What's happening?"
|
||||||
reply-placeholder: "Reply to this post..."
|
reply-placeholder: "Reply to this post..."
|
||||||
|
@ -379,6 +382,7 @@ desktop:
|
||||||
|
|
||||||
mk-settings:
|
mk-settings:
|
||||||
profile: "Profile"
|
profile: "Profile"
|
||||||
|
mute: "Mute"
|
||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
security: "Security"
|
security: "Security"
|
||||||
password: "Password"
|
password: "Password"
|
||||||
|
@ -473,6 +477,11 @@ desktop:
|
||||||
mk-user:
|
mk-user:
|
||||||
last-used-at: "Last used at"
|
last-used-at: "Last used at"
|
||||||
|
|
||||||
|
follows-you: "Follows you"
|
||||||
|
mute: "Mute"
|
||||||
|
muted: "Muting"
|
||||||
|
unmute: "Unmute"
|
||||||
|
|
||||||
photos:
|
photos:
|
||||||
title: "Photos"
|
title: "Photos"
|
||||||
loading: "Loading"
|
loading: "Loading"
|
||||||
|
|
|
@ -346,6 +346,9 @@ desktop:
|
||||||
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
|
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
|
||||||
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
|
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
|
||||||
|
|
||||||
|
mk-mute-setting:
|
||||||
|
no-users: "ミュートしているユーザーはいません"
|
||||||
|
|
||||||
mk-post-form:
|
mk-post-form:
|
||||||
post-placeholder: "いまどうしてる?"
|
post-placeholder: "いまどうしてる?"
|
||||||
reply-placeholder: "この投稿への返信..."
|
reply-placeholder: "この投稿への返信..."
|
||||||
|
@ -379,6 +382,7 @@ desktop:
|
||||||
|
|
||||||
mk-settings:
|
mk-settings:
|
||||||
profile: "プロフィール"
|
profile: "プロフィール"
|
||||||
|
mute: "ミュート"
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
security: "セキュリティ"
|
security: "セキュリティ"
|
||||||
password: "パスワード"
|
password: "パスワード"
|
||||||
|
@ -473,6 +477,11 @@ desktop:
|
||||||
mk-user:
|
mk-user:
|
||||||
last-used-at: "最終アクセス"
|
last-used-at: "最終アクセス"
|
||||||
|
|
||||||
|
follows-you: "フォローされています"
|
||||||
|
mute: "ミュートする"
|
||||||
|
muted: "ミュートしています"
|
||||||
|
unmute: "ミュート解除"
|
||||||
|
|
||||||
photos:
|
photos:
|
||||||
title: "フォト"
|
title: "フォト"
|
||||||
loading: "読み込み中"
|
loading: "読み込み中"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import Notification from '../models/notification';
|
import Notification from '../models/notification';
|
||||||
|
import Mute from '../models/mute';
|
||||||
import event from '../event';
|
import event from '../event';
|
||||||
import serialize from '../serializers/notification';
|
import serialize from '../serializers/notification';
|
||||||
|
|
||||||
|
@ -32,6 +33,17 @@ export default (
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const fresh = await Notification.findOne({ _id: notification._id }, { is_read: true });
|
const fresh = await Notification.findOne({ _id: notification._id }, { is_read: true });
|
||||||
if (!fresh.is_read) {
|
if (!fresh.is_read) {
|
||||||
|
//#region ただしミュートしているユーザーからの通知なら無視
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: notifiee,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mutedUserIds = mute.map(m => m.mutee_id.toString());
|
||||||
|
if (mutedUserIds.indexOf(notifier.toString()) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
event(notifiee, 'unread_notification', await serialize(notification));
|
event(notifiee, 'unread_notification', await serialize(notification));
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
|
@ -222,6 +222,23 @@ const endpoints: Endpoint[] = [
|
||||||
withCredential: true,
|
withCredential: true,
|
||||||
kind: 'notification-read'
|
kind: 'notification-read'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mute/create',
|
||||||
|
withCredential: true,
|
||||||
|
kind: 'account/write'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mute/delete',
|
||||||
|
withCredential: true,
|
||||||
|
kind: 'account/write'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mute/list',
|
||||||
|
withCredential: true,
|
||||||
|
kind: 'account/read'
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'notifications/get_unread_count',
|
name: 'notifications/get_unread_count',
|
||||||
withCredential: true,
|
withCredential: true,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import Notification from '../../models/notification';
|
import Notification from '../../models/notification';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
import serialize from '../../serializers/notification';
|
import serialize from '../../serializers/notification';
|
||||||
import getFriends from '../../common/get-friends';
|
import getFriends from '../../common/get-friends';
|
||||||
import read from '../../common/read-notification';
|
import read from '../../common/read-notification';
|
||||||
|
@ -45,8 +46,18 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
return rej('cannot set since_id and until_id');
|
return rej('cannot set since_id and until_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
notifiee_id: user._id
|
notifiee_id: user._id,
|
||||||
|
$and: [{
|
||||||
|
notifier_id: {
|
||||||
|
$nin: mute.map(m => m.mutee_id)
|
||||||
|
}
|
||||||
|
}]
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const sort = {
|
const sort = {
|
||||||
|
@ -54,12 +65,14 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (following) {
|
if (following) {
|
||||||
// ID list of the user $self and other users who the user follows
|
// ID list of the user itself and other users who the user follows
|
||||||
const followingIds = await getFriends(user._id);
|
const followingIds = await getFriends(user._id);
|
||||||
|
|
||||||
query.notifier_id = {
|
query.$and.push({
|
||||||
$in: followingIds
|
notifier_id: {
|
||||||
};
|
$in: followingIds
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import History from '../../models/messaging-history';
|
import History from '../../models/messaging-history';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
import serialize from '../../serializers/messaging-message';
|
import serialize from '../../serializers/messaging-message';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,10 +18,18 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$;
|
const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$;
|
||||||
if (limitErr) return rej('invalid limit param');
|
if (limitErr) return rej('invalid limit param');
|
||||||
|
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
// Get history
|
// Get history
|
||||||
const history = await History
|
const history = await History
|
||||||
.find({
|
.find({
|
||||||
user_id: user._id
|
user_id: user._id,
|
||||||
|
partner: {
|
||||||
|
$nin: mute.map(m => m.mutee_id)
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
limit: limit,
|
limit: limit,
|
||||||
sort: {
|
sort: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Message from '../../../models/messaging-message';
|
||||||
import { isValidText } from '../../../models/messaging-message';
|
import { isValidText } from '../../../models/messaging-message';
|
||||||
import History from '../../../models/messaging-history';
|
import History from '../../../models/messaging-history';
|
||||||
import User from '../../../models/user';
|
import User from '../../../models/user';
|
||||||
|
import Mute from '../../../models/mute';
|
||||||
import DriveFile from '../../../models/drive-file';
|
import DriveFile from '../../../models/drive-file';
|
||||||
import serialize from '../../../serializers/messaging-message';
|
import serialize from '../../../serializers/messaging-message';
|
||||||
import publishUserStream from '../../../event';
|
import publishUserStream from '../../../event';
|
||||||
|
@ -97,6 +98,17 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true });
|
const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true });
|
||||||
if (!freshMessage.is_read) {
|
if (!freshMessage.is_read) {
|
||||||
|
//#region ただしミュートされているなら発行しない
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: recipient._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mutedUserIds = mute.map(m => m.mutee_id.toString());
|
||||||
|
if (mutedUserIds.indexOf(user._id.toString()) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj);
|
publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj);
|
||||||
pushSw(message.recipient_id, 'unread_messaging_message', messageObj);
|
pushSw(message.recipient_id, 'unread_messaging_message', messageObj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Module dependencies
|
* Module dependencies
|
||||||
*/
|
*/
|
||||||
import Message from '../../models/messaging-message';
|
import Message from '../../models/messaging-message';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get count of unread messages
|
* Get count of unread messages
|
||||||
|
@ -11,8 +12,17 @@ import Message from '../../models/messaging-message';
|
||||||
* @return {Promise<any>}
|
* @return {Promise<any>}
|
||||||
*/
|
*/
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mutedUserIds = mute.map(m => m.mutee_id);
|
||||||
|
|
||||||
const count = await Message
|
const count = await Message
|
||||||
.count({
|
.count({
|
||||||
|
user_id: {
|
||||||
|
$nin: mutedUserIds
|
||||||
|
},
|
||||||
recipient_id: user._id,
|
recipient_id: user._id,
|
||||||
is_read: false
|
is_read: false
|
||||||
});
|
});
|
||||||
|
|
61
src/api/endpoints/mute/create.ts
Normal file
61
src/api/endpoints/mute/create.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* Module dependencies
|
||||||
|
*/
|
||||||
|
import $ from 'cafy';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mute a user
|
||||||
|
*
|
||||||
|
* @param {any} params
|
||||||
|
* @param {any} user
|
||||||
|
* @return {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
const muter = user;
|
||||||
|
|
||||||
|
// Get 'user_id' parameter
|
||||||
|
const [userId, userIdErr] = $(params.user_id).id().$;
|
||||||
|
if (userIdErr) return rej('invalid user_id param');
|
||||||
|
|
||||||
|
// 自分自身
|
||||||
|
if (user._id.equals(userId)) {
|
||||||
|
return rej('mutee is yourself');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await User.findOne({
|
||||||
|
_id: userId
|
||||||
|
}, {
|
||||||
|
fields: {
|
||||||
|
data: false,
|
||||||
|
profile: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mutee === null) {
|
||||||
|
return rej('user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already muting
|
||||||
|
const exist = await Mute.findOne({
|
||||||
|
muter_id: muter._id,
|
||||||
|
mutee_id: mutee._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist !== null) {
|
||||||
|
return rej('already muting');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mute
|
||||||
|
await Mute.insert({
|
||||||
|
created_at: new Date(),
|
||||||
|
muter_id: muter._id,
|
||||||
|
mutee_id: mutee._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
res();
|
||||||
|
});
|
63
src/api/endpoints/mute/delete.ts
Normal file
63
src/api/endpoints/mute/delete.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* Module dependencies
|
||||||
|
*/
|
||||||
|
import $ from 'cafy';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmute a user
|
||||||
|
*
|
||||||
|
* @param {any} params
|
||||||
|
* @param {any} user
|
||||||
|
* @return {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
const muter = user;
|
||||||
|
|
||||||
|
// Get 'user_id' parameter
|
||||||
|
const [userId, userIdErr] = $(params.user_id).id().$;
|
||||||
|
if (userIdErr) return rej('invalid user_id param');
|
||||||
|
|
||||||
|
// Check if the mutee is yourself
|
||||||
|
if (user._id.equals(userId)) {
|
||||||
|
return rej('mutee is yourself');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await User.findOne({
|
||||||
|
_id: userId
|
||||||
|
}, {
|
||||||
|
fields: {
|
||||||
|
data: false,
|
||||||
|
profile: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mutee === null) {
|
||||||
|
return rej('user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check not muting
|
||||||
|
const exist = await Mute.findOne({
|
||||||
|
muter_id: muter._id,
|
||||||
|
mutee_id: mutee._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist === null) {
|
||||||
|
return rej('already not muting');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mute
|
||||||
|
await Mute.update({
|
||||||
|
_id: exist._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
deleted_at: new Date()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
res();
|
||||||
|
});
|
73
src/api/endpoints/mute/list.ts
Normal file
73
src/api/endpoints/mute/list.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Module dependencies
|
||||||
|
*/
|
||||||
|
import $ from 'cafy';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
|
import serialize from '../../serializers/user';
|
||||||
|
import getFriends from '../../common/get-friends';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get muted users of a user
|
||||||
|
*
|
||||||
|
* @param {any} params
|
||||||
|
* @param {any} me
|
||||||
|
* @return {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.exports = (params, me) => new Promise(async (res, rej) => {
|
||||||
|
// Get 'iknow' parameter
|
||||||
|
const [iknow = false, iknowErr] = $(params.iknow).optional.boolean().$;
|
||||||
|
if (iknowErr) return rej('invalid iknow param');
|
||||||
|
|
||||||
|
// Get 'limit' parameter
|
||||||
|
const [limit = 30, limitErr] = $(params.limit).optional.number().range(1, 100).$;
|
||||||
|
if (limitErr) return rej('invalid limit param');
|
||||||
|
|
||||||
|
// Get 'cursor' parameter
|
||||||
|
const [cursor = null, cursorErr] = $(params.cursor).optional.id().$;
|
||||||
|
if (cursorErr) return rej('invalid cursor param');
|
||||||
|
|
||||||
|
// Construct query
|
||||||
|
const query = {
|
||||||
|
muter_id: me._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (iknow) {
|
||||||
|
// Get my friends
|
||||||
|
const myFriends = await getFriends(me._id);
|
||||||
|
|
||||||
|
query.mutee_id = {
|
||||||
|
$in: myFriends
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// カーソルが指定されている場合
|
||||||
|
if (cursor) {
|
||||||
|
query._id = {
|
||||||
|
$lt: cursor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutes
|
||||||
|
const mutes = await Mute
|
||||||
|
.find(query, {
|
||||||
|
limit: limit + 1,
|
||||||
|
sort: { _id: -1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 「次のページ」があるかどうか
|
||||||
|
const inStock = mutes.length === limit + 1;
|
||||||
|
if (inStock) {
|
||||||
|
mutes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
const users = await Promise.all(mutes.map(async m =>
|
||||||
|
await serialize(m.mutee_id, me, { detail: true })));
|
||||||
|
|
||||||
|
// Response
|
||||||
|
res({
|
||||||
|
users: users,
|
||||||
|
next: inStock ? mutes[mutes.length - 1]._id : null,
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,7 @@
|
||||||
* Module dependencies
|
* Module dependencies
|
||||||
*/
|
*/
|
||||||
import Notification from '../../models/notification';
|
import Notification from '../../models/notification';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get count of unread notifications
|
* Get count of unread notifications
|
||||||
|
@ -11,9 +12,18 @@ import Notification from '../../models/notification';
|
||||||
* @return {Promise<any>}
|
* @return {Promise<any>}
|
||||||
*/
|
*/
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mutedUserIds = mute.map(m => m.mutee_id);
|
||||||
|
|
||||||
const count = await Notification
|
const count = await Notification
|
||||||
.count({
|
.count({
|
||||||
notifiee_id: user._id,
|
notifiee_id: user._id,
|
||||||
|
notifier_id: {
|
||||||
|
$nin: mutedUserIds
|
||||||
|
},
|
||||||
is_read: false
|
is_read: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { default as Post, IPost, isValidText } from '../../models/post';
|
||||||
import { default as User, IUser } from '../../models/user';
|
import { default as User, IUser } from '../../models/user';
|
||||||
import { default as Channel, IChannel } from '../../models/channel';
|
import { default as Channel, IChannel } from '../../models/channel';
|
||||||
import Following from '../../models/following';
|
import Following from '../../models/following';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
import DriveFile from '../../models/drive-file';
|
import DriveFile from '../../models/drive-file';
|
||||||
import Watching from '../../models/post-watching';
|
import Watching from '../../models/post-watching';
|
||||||
import ChannelWatching from '../../models/channel-watching';
|
import ChannelWatching from '../../models/channel-watching';
|
||||||
|
@ -215,7 +216,11 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
|
||||||
poll: poll,
|
poll: poll,
|
||||||
text: text,
|
text: text,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
app_id: app ? app._id : null
|
app_id: app ? app._id : null,
|
||||||
|
|
||||||
|
// 以下非正規化データ
|
||||||
|
_reply: reply ? { user_id: reply.user_id } : undefined,
|
||||||
|
_repost: repost ? { user_id: repost.user_id } : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
|
@ -236,7 +241,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
|
||||||
|
|
||||||
const mentions = [];
|
const mentions = [];
|
||||||
|
|
||||||
function addMention(mentionee, reason) {
|
async function addMention(mentionee, reason) {
|
||||||
// Reject if already added
|
// Reject if already added
|
||||||
if (mentions.some(x => x.equals(mentionee))) return;
|
if (mentions.some(x => x.equals(mentionee))) return;
|
||||||
|
|
||||||
|
@ -245,8 +250,15 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
|
||||||
|
|
||||||
// Publish event
|
// Publish event
|
||||||
if (!user._id.equals(mentionee)) {
|
if (!user._id.equals(mentionee)) {
|
||||||
event(mentionee, reason, postObj);
|
const mentioneeMutes = await Mute.find({
|
||||||
pushSw(mentionee, reason, postObj);
|
muter_id: mentionee,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mentioneesMutedUserIds = mentioneeMutes.map(m => m.mutee_id.toString());
|
||||||
|
if (mentioneesMutedUserIds.indexOf(user._id.toString()) == -1) {
|
||||||
|
event(mentionee, reason, postObj);
|
||||||
|
pushSw(mentionee, reason, postObj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import $ from 'cafy';
|
||||||
const escapeRegexp = require('escape-regexp');
|
const escapeRegexp = require('escape-regexp');
|
||||||
import Post from '../../models/post';
|
import Post from '../../models/post';
|
||||||
import User from '../../models/user';
|
import User from '../../models/user';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
import getFriends from '../../common/get-friends';
|
import getFriends from '../../common/get-friends';
|
||||||
import serialize from '../../serializers/post';
|
import serialize from '../../serializers/post';
|
||||||
import config from '../../../conf';
|
import config from '../../../conf';
|
||||||
|
@ -34,6 +35,10 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
|
||||||
const [following = null, followingErr] = $(params.following).optional.nullable.boolean().$;
|
const [following = null, followingErr] = $(params.following).optional.nullable.boolean().$;
|
||||||
if (followingErr) return rej('invalid following param');
|
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
|
// Get 'reply' parameter
|
||||||
const [reply = null, replyErr] = $(params.reply).optional.nullable.boolean().$;
|
const [reply = null, replyErr] = $(params.reply).optional.nullable.boolean().$;
|
||||||
if (replyErr) return rej('invalid reply param');
|
if (replyErr) return rej('invalid reply param');
|
||||||
|
@ -80,11 +85,11 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
|
||||||
// If Elasticsearch is available, search by it
|
// If Elasticsearch is available, search by it
|
||||||
// If not, search by MongoDB
|
// If not, search by MongoDB
|
||||||
(config.elasticsearch.enable ? byElasticsearch : byNative)
|
(config.elasticsearch.enable ? byElasticsearch : byNative)
|
||||||
(res, rej, me, text, user, following, reply, repost, media, poll, sinceDate, untilDate, offset, limit);
|
(res, rej, me, text, user, following, mute, reply, repost, media, poll, sinceDate, untilDate, offset, limit);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Search by MongoDB
|
// Search by MongoDB
|
||||||
async function byNative(res, rej, me, text, userId, following, reply, repost, media, poll, sinceDate, untilDate, offset, max) {
|
async function byNative(res, rej, me, text, userId, following, mute, reply, repost, media, poll, sinceDate, untilDate, offset, max) {
|
||||||
let q: any = {
|
let q: any = {
|
||||||
$and: []
|
$and: []
|
||||||
};
|
};
|
||||||
|
@ -116,6 +121,84 @@ async function byNative(res, rej, me, text, userId, following, reply, repost, me
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (reply != null) {
|
if (reply != null) {
|
||||||
if (reply) {
|
if (reply) {
|
||||||
push({
|
push({
|
||||||
|
@ -236,7 +319,7 @@ async function byNative(res, rej, me, text, userId, following, reply, repost, me
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search by Elasticsearch
|
// Search by Elasticsearch
|
||||||
async function byElasticsearch(res, rej, me, text, userId, following, reply, repost, media, poll, sinceDate, untilDate, offset, max) {
|
async function byElasticsearch(res, rej, me, text, userId, following, mute, reply, repost, media, poll, sinceDate, untilDate, offset, max) {
|
||||||
const es = require('../../db/elasticsearch');
|
const es = require('../../db/elasticsearch');
|
||||||
|
|
||||||
es.search({
|
es.search({
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import Post from '../../models/post';
|
import Post from '../../models/post';
|
||||||
|
import Mute from '../../models/mute';
|
||||||
import ChannelWatching from '../../models/channel-watching';
|
import ChannelWatching from '../../models/channel-watching';
|
||||||
import getFriends from '../../common/get-friends';
|
import getFriends from '../../common/get-friends';
|
||||||
import serialize from '../../serializers/post';
|
import serialize from '../../serializers/post';
|
||||||
|
@ -42,15 +43,23 @@ module.exports = async (params, user, app) => {
|
||||||
throw 'only one of since_id, until_id, since_date, until_date can be specified';
|
throw 'only one of since_id, until_id, since_date, until_date can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { followingIds, watchingChannelIds } = await rap({
|
const { followingIds, watchingChannelIds, mutedUserIds } = await rap({
|
||||||
// ID list of the user itself and other users who the user follows
|
// ID list of the user itself and other users who the user follows
|
||||||
followingIds: getFriends(user._id),
|
followingIds: getFriends(user._id),
|
||||||
|
|
||||||
// Watchしているチャンネルを取得
|
// Watchしているチャンネルを取得
|
||||||
watchingChannelIds: ChannelWatching.find({
|
watchingChannelIds: ChannelWatching.find({
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
// 削除されたドキュメントは除く
|
// 削除されたドキュメントは除く
|
||||||
deleted_at: { $exists: false }
|
deleted_at: { $exists: false }
|
||||||
}).then(watches => watches.map(w => w.channel_id))
|
}).then(watches => watches.map(w => w.channel_id)),
|
||||||
|
|
||||||
|
// ミュートしているユーザーを取得
|
||||||
|
mutedUserIds: Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
// 削除されたドキュメントは除く
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
}).then(ms => ms.map(m => m.mutee_id))
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region Construct query
|
//#region Construct query
|
||||||
|
@ -77,7 +86,17 @@ module.exports = async (params, user, app) => {
|
||||||
channel_id: {
|
channel_id: {
|
||||||
$in: watchingChannelIds
|
$in: watchingChannelIds
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
// mute
|
||||||
|
user_id: {
|
||||||
|
$nin: mutedUserIds
|
||||||
|
},
|
||||||
|
'_reply.user_id': {
|
||||||
|
$nin: mutedUserIds
|
||||||
|
},
|
||||||
|
'_repost.user_id': {
|
||||||
|
$nin: mutedUserIds
|
||||||
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
if (sinceId) {
|
if (sinceId) {
|
||||||
|
|
3
src/api/models/mute.ts
Normal file
3
src/api/models/mute.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import db from '../../db/mongodb';
|
||||||
|
|
||||||
|
export default db.get('mute') as any; // fuck type definition
|
|
@ -6,6 +6,7 @@ import deepcopy = require('deepcopy');
|
||||||
import { default as User, IUser } from '../models/user';
|
import { default as User, IUser } from '../models/user';
|
||||||
import serializePost from './post';
|
import serializePost from './post';
|
||||||
import Following from '../models/following';
|
import Following from '../models/following';
|
||||||
|
import Mute from '../models/mute';
|
||||||
import getFriends from '../common/get-friends';
|
import getFriends from '../common/get-friends';
|
||||||
import config from '../../conf';
|
import config from '../../conf';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
|
@ -113,7 +114,7 @@ export default (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meId && !meId.equals(_user.id)) {
|
if (meId && !meId.equals(_user.id)) {
|
||||||
// If the user is following
|
// Whether the user is following
|
||||||
_user.is_following = (async () => {
|
_user.is_following = (async () => {
|
||||||
const follow = await Following.findOne({
|
const follow = await Following.findOne({
|
||||||
follower_id: meId,
|
follower_id: meId,
|
||||||
|
@ -123,7 +124,7 @@ export default (
|
||||||
return follow !== null;
|
return follow !== null;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// If the user is followed
|
// Whether the user is followed
|
||||||
_user.is_followed = (async () => {
|
_user.is_followed = (async () => {
|
||||||
const follow2 = await Following.findOne({
|
const follow2 = await Following.findOne({
|
||||||
follower_id: _user.id,
|
follower_id: _user.id,
|
||||||
|
@ -132,6 +133,16 @@ export default (
|
||||||
});
|
});
|
||||||
return follow2 !== null;
|
return follow2 !== null;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Whether the user is muted
|
||||||
|
_user.is_muted = (async () => {
|
||||||
|
const mute = await Mute.findOne({
|
||||||
|
muter_id: meId,
|
||||||
|
mutee_id: _user.id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
return mute !== null;
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.detail) {
|
if (opts.detail) {
|
||||||
|
|
|
@ -3,19 +3,48 @@ import * as redis from 'redis';
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
|
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
|
import Mute from '../models/mute';
|
||||||
import serializePost from '../serializers/post';
|
import serializePost from '../serializers/post';
|
||||||
import readNotification from '../common/read-notification';
|
import readNotification from '../common/read-notification';
|
||||||
|
|
||||||
const log = debug('misskey');
|
const log = debug('misskey');
|
||||||
|
|
||||||
export default function homeStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
|
export default async function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any) {
|
||||||
// Subscribe Home stream channel
|
// Subscribe Home stream channel
|
||||||
subscriber.subscribe(`misskey:user-stream:${user._id}`);
|
subscriber.subscribe(`misskey:user-stream:${user._id}`);
|
||||||
|
|
||||||
|
const mute = await Mute.find({
|
||||||
|
muter_id: user._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
const mutedUserIds = mute.map(m => m.mutee_id.toString());
|
||||||
|
|
||||||
subscriber.on('message', async (channel, data) => {
|
subscriber.on('message', async (channel, data) => {
|
||||||
switch (channel.split(':')[1]) {
|
switch (channel.split(':')[1]) {
|
||||||
case 'user-stream':
|
case 'user-stream':
|
||||||
connection.send(data);
|
try {
|
||||||
|
const x = JSON.parse(data);
|
||||||
|
|
||||||
|
if (x.type == 'post') {
|
||||||
|
if (mutedUserIds.indexOf(x.body.user_id) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x.body.reply != null && mutedUserIds.indexOf(x.body.reply.user_id) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x.body.repost != null && mutedUserIds.indexOf(x.body.repost.user_id) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (x.type == 'notification') {
|
||||||
|
if (mutedUserIds.indexOf(x.body.user_id) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.send(data);
|
||||||
|
} catch (e) {
|
||||||
|
connection.send(data);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'post-stream':
|
case 'post-stream':
|
||||||
const postId = channel.split(':')[2];
|
const postId = channel.split(':')[2];
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<p class={ active: page == 'web' } onmousedown={ setPage.bind(null, 'web') }>%fa:desktop .fw%Web</p>
|
<p class={ active: page == 'web' } onmousedown={ setPage.bind(null, 'web') }>%fa:desktop .fw%Web</p>
|
||||||
<p class={ active: page == 'notification' } onmousedown={ setPage.bind(null, 'notification') }>%fa:R bell .fw%通知</p>
|
<p class={ active: page == 'notification' } onmousedown={ setPage.bind(null, 'notification') }>%fa:R bell .fw%通知</p>
|
||||||
<p class={ active: page == 'drive' } onmousedown={ setPage.bind(null, 'drive') }>%fa:cloud .fw%%i18n:desktop.tags.mk-settings.drive%</p>
|
<p class={ active: page == 'drive' } onmousedown={ setPage.bind(null, 'drive') }>%fa:cloud .fw%%i18n:desktop.tags.mk-settings.drive%</p>
|
||||||
|
<p class={ active: page == 'mute' } onmousedown={ setPage.bind(null, 'mute') }>%fa:ban .fw%%i18n:desktop.tags.mk-settings.mute%</p>
|
||||||
<p class={ active: page == 'apps' } onmousedown={ setPage.bind(null, 'apps') }>%fa:puzzle-piece .fw%アプリ</p>
|
<p class={ active: page == 'apps' } onmousedown={ setPage.bind(null, 'apps') }>%fa:puzzle-piece .fw%アプリ</p>
|
||||||
<p class={ active: page == 'twitter' } onmousedown={ setPage.bind(null, 'twitter') }>%fa:B twitter .fw%Twitter</p>
|
<p class={ active: page == 'twitter' } onmousedown={ setPage.bind(null, 'twitter') }>%fa:B twitter .fw%Twitter</p>
|
||||||
<p class={ active: page == 'security' } onmousedown={ setPage.bind(null, 'security') }>%fa:unlock-alt .fw%%i18n:desktop.tags.mk-settings.security%</p>
|
<p class={ active: page == 'security' } onmousedown={ setPage.bind(null, 'security') }>%fa:unlock-alt .fw%%i18n:desktop.tags.mk-settings.security%</p>
|
||||||
|
@ -26,6 +27,11 @@
|
||||||
<mk-drive-setting/>
|
<mk-drive-setting/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="mute" show={ page == 'mute' }>
|
||||||
|
<h1>%i18n:desktop.tags.mk-settings.mute%</h1>
|
||||||
|
<mk-mute-setting/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="apps" show={ page == 'apps' }>
|
<section class="apps" show={ page == 'apps' }>
|
||||||
<h1>アプリケーション</h1>
|
<h1>アプリケーション</h1>
|
||||||
<mk-authorized-apps/>
|
<mk-authorized-apps/>
|
||||||
|
@ -386,3 +392,35 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</mk-drive-setting>
|
</mk-drive-setting>
|
||||||
|
|
||||||
|
<mk-mute-setting>
|
||||||
|
<div class="none ui info" if={ !fetching && users.length == 0 }>
|
||||||
|
<p>%fa:info-circle%%i18n:desktop.tags.mk-mute-setting.no-users%</p>
|
||||||
|
</div>
|
||||||
|
<div class="users" if={ users.length != 0 }>
|
||||||
|
<div each={ user in users }>
|
||||||
|
<p><b>{ user.name }</b> @{ user.username }</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:scope
|
||||||
|
display block
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
this.mixin('api');
|
||||||
|
|
||||||
|
this.apps = [];
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
this.on('mount', () => {
|
||||||
|
this.api('mute/list').then(x => {
|
||||||
|
this.update({
|
||||||
|
fetching: false,
|
||||||
|
users: x.users
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</mk-mute-setting>
|
||||||
|
|
|
@ -226,7 +226,9 @@
|
||||||
<mk-user-profile>
|
<mk-user-profile>
|
||||||
<div class="friend-form" if={ SIGNIN && I.id != user.id }>
|
<div class="friend-form" if={ SIGNIN && I.id != user.id }>
|
||||||
<mk-big-follow-button user={ user }/>
|
<mk-big-follow-button user={ user }/>
|
||||||
<p class="followed" if={ user.is_followed }>フォローされています</p>
|
<p class="followed" if={ user.is_followed }>%i18n:desktop.tags.mk-user.follows-you%</p>
|
||||||
|
<p if={ user.is_muted }>%i18n:desktop.tags.mk-user.muted% <a onclick={ unmute }>%i18n:desktop.tags.mk-user.unmute%</a></p>
|
||||||
|
<p if={ !user.is_muted }><a onclick={ mute }>%i18n:desktop.tags.mk-user.mute%</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="description" if={ user.description }>{ user.description }</div>
|
<div class="description" if={ user.description }>{ user.description }</div>
|
||||||
<div class="birthday" if={ user.profile.birthday }>
|
<div class="birthday" if={ user.profile.birthday }>
|
||||||
|
@ -311,6 +313,7 @@
|
||||||
this.age = require('s-age');
|
this.age = require('s-age');
|
||||||
|
|
||||||
this.mixin('i');
|
this.mixin('i');
|
||||||
|
this.mixin('api');
|
||||||
|
|
||||||
this.user = this.opts.user;
|
this.user = this.opts.user;
|
||||||
|
|
||||||
|
@ -325,6 +328,28 @@
|
||||||
user: this.user
|
user: this.user
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.mute = () => {
|
||||||
|
this.api('mute/create', {
|
||||||
|
user_id: this.user.id
|
||||||
|
}).then(() => {
|
||||||
|
this.user.is_muted = true;
|
||||||
|
this.update();
|
||||||
|
}, e => {
|
||||||
|
alert('error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unmute = () => {
|
||||||
|
this.api('mute/delete', {
|
||||||
|
user_id: this.user.id
|
||||||
|
}).then(() => {
|
||||||
|
this.user.is_muted = false;
|
||||||
|
this.update();
|
||||||
|
}, e => {
|
||||||
|
alert('error');
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</mk-user-profile>
|
</mk-user-profile>
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,12 @@ props:
|
||||||
optional: true
|
optional: true
|
||||||
desc:
|
desc:
|
||||||
ja: "自分がこのユーザーにフォローされているか"
|
ja: "自分がこのユーザーにフォローされているか"
|
||||||
|
- name: "is_muted"
|
||||||
|
type: "boolean"
|
||||||
|
optional: true
|
||||||
|
desc:
|
||||||
|
ja: "自分がこのユーザーをミュートしているか"
|
||||||
|
en: "Whether you muted this user"
|
||||||
- name: "last_used_at"
|
- name: "last_used_at"
|
||||||
type: "date"
|
type: "date"
|
||||||
optional: false
|
optional: false
|
||||||
|
|
13
src/web/docs/mute.ja.pug
Normal file
13
src/web/docs/mute.ja.pug
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
h1 ミュート
|
||||||
|
|
||||||
|
p ユーザーページから、そのユーザーをミュートすることができます。
|
||||||
|
|
||||||
|
p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります:
|
||||||
|
ul
|
||||||
|
li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRepost)
|
||||||
|
li そのユーザーからの通知
|
||||||
|
li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴
|
||||||
|
|
||||||
|
p ミュートを行ったことは相手に通知されず、ミュートされていることを知ることもできません。
|
||||||
|
|
||||||
|
p 設定>ミュート から、自分がミュートしているユーザー一覧を確認することができます。
|
|
@ -29,6 +29,22 @@ section
|
||||||
| false ... フォローしていないユーザーに限定。
|
| false ... フォローしていないユーザーに限定。
|
||||||
br
|
br
|
||||||
| null ... 特に限定しない(デフォルト)
|
| null ... 特に限定しない(デフォルト)
|
||||||
|
tr
|
||||||
|
td mute
|
||||||
|
td
|
||||||
|
| mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostを除外する(デフォルト)
|
||||||
|
br
|
||||||
|
| mute_related ... ミュートしているユーザーの投稿に対する返信やRepostだけ除外する
|
||||||
|
br
|
||||||
|
| mute_direct ... ミュートしているユーザーの投稿だけ除外する
|
||||||
|
br
|
||||||
|
| disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostも含める
|
||||||
|
br
|
||||||
|
| direct_only ... ミュートしているユーザーの投稿だけに限定
|
||||||
|
br
|
||||||
|
| related_only ... ミュートしているユーザーの投稿に対する返信やRepostだけに限定
|
||||||
|
br
|
||||||
|
| all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostに限定
|
||||||
tr
|
tr
|
||||||
td reply
|
td reply
|
||||||
td
|
td
|
||||||
|
|
67
tools/migration/node.2017-12-22.hiseikika.js
Normal file
67
tools/migration/node.2017-12-22.hiseikika.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// for Node.js interpret
|
||||||
|
|
||||||
|
const { default: Post } = require('../../built/api/models/post')
|
||||||
|
const { default: zip } = require('@prezzemolo/zip')
|
||||||
|
|
||||||
|
const migrate = async (post) => {
|
||||||
|
const x = {};
|
||||||
|
if (post.reply_id != null) {
|
||||||
|
const reply = await Post.findOne({
|
||||||
|
_id: post.reply_id
|
||||||
|
});
|
||||||
|
x['_reply.user_id'] = reply.user_id;
|
||||||
|
}
|
||||||
|
if (post.repost_id != null) {
|
||||||
|
const repost = await Post.findOne({
|
||||||
|
_id: post.repost_id
|
||||||
|
});
|
||||||
|
x['_repost.user_id'] = repost.user_id;
|
||||||
|
}
|
||||||
|
if (post.reply_id != null || post.repost_id != null) {
|
||||||
|
const result = await Post.update(post._id, {
|
||||||
|
$set: x,
|
||||||
|
});
|
||||||
|
return result.ok === 1;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const query = {
|
||||||
|
$or: [{
|
||||||
|
reply_id: {
|
||||||
|
$exists: true,
|
||||||
|
$ne: null
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
repost_id: {
|
||||||
|
$exists: true,
|
||||||
|
$ne: null
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = await Post.count(query);
|
||||||
|
|
||||||
|
const dop = Number.parseInt(process.argv[2]) || 5
|
||||||
|
const idop = ((count - (count % dop)) / dop) + 1
|
||||||
|
|
||||||
|
return zip(
|
||||||
|
1,
|
||||||
|
async (time) => {
|
||||||
|
console.log(`${time} / ${idop}`)
|
||||||
|
const doc = await Post.find(query, {
|
||||||
|
limit: dop, skip: time * dop
|
||||||
|
})
|
||||||
|
return Promise.all(doc.map(migrate))
|
||||||
|
},
|
||||||
|
idop
|
||||||
|
).then(a => {
|
||||||
|
const rv = []
|
||||||
|
a.forEach(e => rv.push(...e))
|
||||||
|
return rv
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(console.dir).catch(console.error)
|
Loading…
Reference in a new issue