diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index b6a8e9b661..dd36d32e74 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 @@ 自分のフォロワーにのみ公開 -
+
%fa:envelope%
- メンション - 言及したユーザーにのみ公開 + ダイレクト + 指定したユーザーにのみ公開
diff --git a/src/models/note.ts b/src/models/note.ts index 2f95cbfd65..5c4ac86353 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('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 af4f365226..52c6068fd0 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 4808edfda4..e8070595cd 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(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,