diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b24dda7..fe8068950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。 - Misskey更新時にダイアログを表示するように - ジョブキューウィジェットに警報音を鳴らす設定を追加 +- ブロックの挙動を改修 + - ブロックされたユーザーがブロックしたユーザーに対してアクション出来ないようになりました。詳細はドキュメントをご確認ください。 - UIデザインの調整 - データベースのインデックスを最適化 - Proxy使用時にKeep-Aliveをサポート diff --git a/src/docs/ja-JP/features/mute-and-block.md b/src/docs/ja-JP/features/mute-and-block.md new file mode 100644 index 000000000..4a5844085 --- /dev/null +++ b/src/docs/ja-JP/features/mute-and-block.md @@ -0,0 +1,43 @@ +# ミュートとブロック +好みではないユーザーがいる場合は、ミュートを行うことでそのユーザーが自分から見えないようにすることができます。 +また、より強力な措置として、ブロックを行うことでそのユーザーから自分のコンテンツが見えないようになるほか、自分に対して関わることができないようにすることができます。 +ミュートされていることは相手は分かりませんが、ブロックされていることは相手に分かります。どちらを選ぶかはご自身の判断で行ってください。 + +
ℹ️ ミュートとブロックは併用できます。
+ +
⚠️ 利用規約に違反するような、迷惑なユーザーがいる場合は運営者に報告することも検討してください。
+ +設定>ミュートとブロック から、自分がミュートまたはブロックしているユーザー一覧を確認することができます。 + +## ミュート +ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります: + +- タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote) +- そのユーザーからの通知 +- メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴 +- など + +ユーザーをミュートするには、対象のユーザーのユーザーページのメニューを開き、「ミュート」ボタンを押します。 + +
ℹ️ ミュートを行ったことは相手に通知されず、ミュートされていることを知ることもできません。
+ +## ブロック +ユーザーをブロックすると、そのユーザーからあなたのコンテンツが見えないようになり、またあなたに対して以下のようなアクションをすることができなくなります。 + +- フォローする +- ユーザーリストに追加する +- 返信する、Renoteする +- リアクションする、アンケートに投票する +- メッセージを送信する +- など + +また、 + +- ブロックする際に既にそのユーザーからフォローされていた場合はフォローが解除されます。 +- ブロックする際に既にそのユーザーがあなたをユーザーリストに入れていた場合はそのリストからあなたが削除されます。 + +ユーザーをブロックするには、対象のユーザーのユーザーページのメニューを開き、「ブロック」ボタンを押します。 + +
⚠️ ブロックを行ったこと自体は相手に通知されませんが、フォローを行ったりなどの上記のアクションが行えなくなるので間接的にブロックされていることは分かります。
+ +
⚠️ 相手から自分のコンテンツが見えなくなりますが、相手がアカウントを切り替えたりログアウト状態になれば見ることができます。あくまで簡易的、補助的なものとしてお考えください。
diff --git a/src/docs/ja-JP/features/mute.md b/src/docs/ja-JP/features/mute.md deleted file mode 100644 index 6a9608662..000000000 --- a/src/docs/ja-JP/features/mute.md +++ /dev/null @@ -1,13 +0,0 @@ -# ミュート - -ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります: - -* タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote) -* そのユーザーからの通知 -* メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴 - -ユーザーをミュートするには、対象のユーザーのユーザーページに表示されている「ミュート」ボタンを押します。 - -ミュートを行ったことは相手に通知されず、ミュートされていることを知ることもできません。 - -設定>ミュート から、自分がミュートしているユーザー一覧を確認することができます。 diff --git a/src/misc/is-blocker-user-related.ts b/src/misc/is-blocker-user-related.ts new file mode 100644 index 000000000..8c0ebfad9 --- /dev/null +++ b/src/misc/is-blocker-user-related.ts @@ -0,0 +1,15 @@ +export function isBlockerUserRelated(note: any, blockerUserIds: Set): boolean { + if (blockerUserIds.has(note.userId)) { + return true; + } + + if (note.reply != null && blockerUserIds.has(note.reply.userId)) { + return true; + } + + if (note.renote != null && blockerUserIds.has(note.renote.userId)) { + return true; + } + + return false; +} diff --git a/src/server/api/common/generate-block-query.ts b/src/server/api/common/generate-block-query.ts index fa2179ae6..016da57aa 100644 --- a/src/server/api/common/generate-block-query.ts +++ b/src/server/api/common/generate-block-query.ts @@ -1,6 +1,29 @@ import { User } from '../../../models/entities/user'; import { Blockings } from '../../../models'; -import { SelectQueryBuilder } from 'typeorm'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +// ここでいうBlockedは被Blockedの意 +export function generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { + const blockingQuery = Blockings.createQueryBuilder('blocking') + .select('blocking.blockerId') + .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); + + // 投稿の作者にブロックされていない かつ + // 投稿の返信先の作者にブロックされていない かつ + // 投稿の引用元の作者にブロックされていない + q + .andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`) + .andWhere(new Brackets(qb => { qb + .where(`note.replyUserId IS NULL`) + .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`); + })) + .andWhere(new Brackets(qb => { qb + .where(`note.renoteUserId IS NULL`) + .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); + })); + + q.setParameters(blockingQuery.getParameters()); +} export function generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }) { const blockingQuery = Blockings.createQueryBuilder('blocking') diff --git a/src/server/api/common/inject-featured.ts b/src/server/api/common/inject-featured.ts index bbed7f69c..3659b7f2b 100644 --- a/src/server/api/common/inject-featured.ts +++ b/src/server/api/common/inject-featured.ts @@ -3,6 +3,7 @@ import { Note } from '../../../models/entities/note'; import { User } from '../../../models/entities/user'; import { Notes, UserProfiles, NoteReactions } from '../../../models'; import { generateMutedUserQuery } from './generate-muted-user-query'; +import { generateBlockedUserQuery } from './generate-block-query'; // TODO: リアクション、Renote、返信などをしたノートは除外する @@ -29,6 +30,7 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { query.andWhere('note.userId != :userId', { userId: user.id }); generateMutedUserQuery(query, user); + generateBlockedUserQuery(query, user); const reactionQuery = NoteReactions.createQueryBuilder('reaction') .select('reaction.noteId') diff --git a/src/server/api/endpoints/antennas/notes.ts b/src/server/api/endpoints/antennas/notes.ts index a244f7f9b..aadb4261e 100644 --- a/src/server/api/endpoints/antennas/notes.ts +++ b/src/server/api/endpoints/antennas/notes.ts @@ -6,6 +6,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { ApiError } from '../../error'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['antennas', 'account', 'notes'], @@ -77,6 +78,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); + generateBlockedUserQuery(query, user); const notes = await query .take(ps.limit!) diff --git a/src/server/api/endpoints/clips/notes.ts b/src/server/api/endpoints/clips/notes.ts index 5fd17584d..4bece5a2c 100644 --- a/src/server/api/endpoints/clips/notes.ts +++ b/src/server/api/endpoints/clips/notes.ts @@ -6,6 +6,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { ApiError } from '../../error'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -81,6 +82,7 @@ export default define(meta, async (ps, user) => { if (user) { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); + generateBlockedUserQuery(query, user); } const notes = await query diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index 1cd50145b..a00513a24 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -3,7 +3,7 @@ import { ID } from '@/misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; -import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings } from '../../../../../models'; +import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings, Blockings } from '../../../../../models'; import { User } from '../../../../../models/entities/user'; import { UserGroup } from '../../../../../models/entities/user-group'; import { createMessage } from '../../../../../services/messages/create'; @@ -74,7 +74,13 @@ export const meta = { message: 'Content required. You need to set text or fileId.', code: 'CONTENT_REQUIRED', id: '25587321-b0e6-449c-9239-f8925092942c' - } + }, + + youHaveBeenBlocked: { + message: 'You cannot send a message because you have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: 'c15a5199-7422-4968-941a-2a462c478f7d' + }, } }; @@ -93,6 +99,15 @@ export default define(meta, async (ps, user) => { if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); throw e; }); + + // Check blocking + const block = await Blockings.findOne({ + blockerId: recipientUser.id, + blockeeId: user.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } } else if (ps.groupId != null) { // Fetch recipient (group) recipientGroup = await UserGroups.findOne(ps.groupId); diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index adbe714bf..f4d295881 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -6,6 +6,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { Brackets } from 'typeorm'; import { Notes } from '../../../../models'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -63,6 +64,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); if (user) generateMutedUserQuery(query, user); + if (user) generateBlockedUserQuery(query, user); const notes = await query.take(ps.limit!).getMany(); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index ddb5c953e..9c055683f 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -7,7 +7,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { ApiError } from '../../error'; import { ID } from '@/misc/cafy-id'; import { User } from '../../../../models/entities/user'; -import { Users, DriveFiles, Notes, Channels } from '../../../../models'; +import { Users, DriveFiles, Notes, Channels, Blockings } from '../../../../models'; import { DriveFile } from '../../../../models/entities/drive-file'; import { Note } from '../../../../models/entities/note'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits'; @@ -171,6 +171,12 @@ export const meta = { code: 'NO_SUCH_CHANNEL', id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb' }, + + youHaveBeenBlocked: { + message: 'You have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3' + }, } }; @@ -202,6 +208,17 @@ export default define(meta, async (ps, user) => { } else if (renote.renoteId && !renote.text && !renote.fileIds) { throw new ApiError(meta.errors.cannotReRenote); } + + // Check blocking + if (renote.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: renote.userId, + blockeeId: user.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } let reply: Note | undefined; @@ -217,6 +234,17 @@ export default define(meta, async (ps, user) => { if (reply.renoteId && !reply.text && !reply.fileIds) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } + + // Check blocking + if (reply.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: reply.userId, + blockeeId: user.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } if (ps.poll) { diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 5b4367f7a..44c0fb23a 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../define'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { Notes } from '../../../../models'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -48,6 +49,7 @@ export default define(meta, async (ps, user) => { .leftJoinAndSelect('renote.user', 'renoteUser'); if (user) generateMutedUserQuery(query, user); + if (user) generateBlockedUserQuery(query, user); let notes = await query .orderBy('note.score', 'DESC') diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 741c5985a..96bfde5aa 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -9,6 +9,7 @@ import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { activeUsersChart } from '../../../../services/chart'; import { generateRepliesQuery } from '../../common/generate-replies-query'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -81,6 +82,7 @@ export default define(meta, async (ps, user) => { generateRepliesQuery(query, user); if (user) generateMutedUserQuery(query, user); if (user) generateMutedNoteQuery(query, user); + if (user) generateBlockedUserQuery(query, user); if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 23fc5a606..91a36fd0c 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -12,6 +12,7 @@ import { activeUsersChart } from '../../../../services/chart'; import { generateRepliesQuery } from '../../common/generate-replies-query'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query'; import { generateChannelQuery } from '../../common/generate-channel-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -108,6 +109,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); generateMutedNoteQuery(query, user); + generateBlockedUserQuery(query, user); if (ps.includeMyRenotes === false) { query.andWhere(new Brackets(qb => { diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 523fbee9a..4f481b599 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -12,6 +12,7 @@ import { Brackets } from 'typeorm'; import { generateRepliesQuery } from '../../common/generate-replies-query'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query'; import { generateChannelQuery } from '../../common/generate-channel-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -94,6 +95,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); if (user) generateMutedUserQuery(query, user); if (user) generateMutedNoteQuery(query, user); + if (user) generateBlockedUserQuery(query, user); if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index e1e916c58..6a2358228 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -7,6 +7,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Brackets } from 'typeorm'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -66,6 +67,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); + generateBlockedUserQuery(query, user); if (ps.visibility) { query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index b40e187fd..6f2892960 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -9,7 +9,7 @@ import { deliver } from '../../../../../queue'; import { renderActivity } from '../../../../../remote/activitypub/renderer'; import renderVote from '../../../../../remote/activitypub/renderer/vote'; import { deliverQuestionUpdate } from '../../../../../services/note/polls/update'; -import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models'; +import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '../../../../../models'; import { Not } from 'typeorm'; import { IRemoteUser } from '../../../../../models/entities/user'; import { genId } from '@/misc/gen-id'; @@ -61,6 +61,12 @@ export const meta = { code: 'ALREADY_EXPIRED', id: '1022a357-b085-4054-9083-8f8de358337e' }, + + youHaveBeenBlocked: { + message: 'You cannot vote this poll because you have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: '85a5377e-b1e9-4617-b0b9-5bea73331e49' + }, } }; @@ -77,6 +83,17 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.noPoll); } + // Check blocking + if (note.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: note.userId, + blockeeId: user.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } + const poll = await Polls.findOneOrFail({ noteId: note.id }); if (poll.expiresAt && poll.expiresAt < createdAt) { @@ -103,13 +120,13 @@ export default define(meta, async (ps, user) => { } // Create vote - const vote = await PollVotes.save({ + const vote = await PollVotes.insert({ id: genId(), createdAt, noteId: note.id, userId: user.id, choice: ps.choice - }); + }).then(x => PollVotes.findOneOrFail(x.identifiers[0])); // Increment votes count const index = ps.choice + 1; // In SQL, array index is 1 based diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index e8533fee5..3243332c5 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -33,7 +33,13 @@ export const meta = { message: 'You are already reacting to that note.', code: 'ALREADY_REACTED', id: '71efcf98-86d6-4e2b-b2ad-9d032369366b' - } + }, + + youHaveBeenBlocked: { + message: 'You cannot react this note because you have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: '20ef5475-9f38-4e4c-bd33-de6d979498ec' + }, } }; @@ -44,6 +50,7 @@ export default define(meta, async (ps, user) => { }); await createReaction(user, note, ps.reaction).catch(e => { if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); + if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); throw e; }); return; diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index d384b7962..5e3b3ccbc 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -7,6 +7,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notes } from '../../../../models'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -67,6 +68,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); if (user) generateMutedUserQuery(query, user); + if (user) generateBlockedUserQuery(query, user); const renotes = await query.take(ps.limit!).getMany(); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 79a983e75..7960078c8 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -5,6 +5,7 @@ import { Notes } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -52,6 +53,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); if (user) generateMutedUserQuery(query, user); + if (user) generateBlockedUserQuery(query, user); const timeline = await query.take(ps.limit!).getMany(); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index bbada1761..39d99baba 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -8,6 +8,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { Brackets } from 'typeorm'; import { safeForSql } from '@/misc/safe-for-sql'; import { normalizeForSearch } from '@/misc/normalize-for-search'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes', 'hashtags'], @@ -75,6 +76,7 @@ export default define(meta, async (ps, me) => { generateVisibilityQuery(query, me); if (me) generateMutedUserQuery(query, me); + if (me) generateBlockedUserQuery(query, me); try { if (ps.tag) { diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index dc411283a..0e0eaa06a 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -8,6 +8,7 @@ import config from '@/config'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -82,6 +83,7 @@ export default define(meta, async (ps, me) => { generateVisibilityQuery(query, me); if (me) generateMutedUserQuery(query, me); + if (me) generateBlockedUserQuery(query, me); const notes = await query.take(ps.limit!).getMany(); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 687869a63..5f0340091 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -10,6 +10,7 @@ import { Brackets } from 'typeorm'; import { generateRepliesQuery } from '../../common/generate-replies-query'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query'; import { generateChannelQuery } from '../../common/generate-channel-query'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], @@ -100,6 +101,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); generateMutedNoteQuery(query, user); + generateBlockedUserQuery(query, user); if (ps.includeMyRenotes === false) { query.andWhere(new Brackets(qb => { diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index 933eb70b6..3c30f459d 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../define'; import { Users } from '../../../models'; import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query'; +import { generateBlockedUserQuery } from '../common/generate-block-query'; export const meta = { tags: ['users'], @@ -89,6 +90,7 @@ export default define(meta, async (ps, me) => { } if (me) generateMutedUserQueryForUsers(query, me); + if (me) generateBlockedUserQuery(query, me); query.take(ps.limit!); query.skip(ps.offset); diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index b81d5b8c7..7bb6fc7f7 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; import { pushUserToUserList } from '../../../../../services/user-list/push'; -import { UserLists, UserListJoinings } from '../../../../../models'; +import { UserLists, UserListJoinings, Blockings } from '../../../../../models'; export const meta = { tags: ['lists', 'users'], @@ -40,7 +40,13 @@ export const meta = { message: 'That user has already been added to that list.', code: 'ALREADY_ADDED', id: '1de7c884-1595-49e9-857e-61f12f4d4fc5' - } + }, + + youHaveBeenBlocked: { + message: 'You cannot push this user because you have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b' + }, } }; @@ -61,6 +67,17 @@ export default define(meta, async (ps, me) => { throw e; }); + // Check blocking + if (user.id !== me.id) { + const block = await Blockings.findOne({ + blockerId: user.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } + const exist = await UserListJoinings.findOne({ userListId: userList.id, userId: user.id diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 55f07e390..836c3c97b 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -8,6 +8,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { Notes } from '../../../../models'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { Brackets } from 'typeorm'; +import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['users', 'notes'], @@ -100,6 +101,7 @@ export default define(meta, async (ps, me) => { generateVisibilityQuery(query, me); if (me) generateMutedUserQuery(query, me, user); + if (me) generateBlockedUserQuery(query, me); if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 7c269268b..fba4f4f68 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -3,7 +3,7 @@ import $ from 'cafy'; import define from '../../define'; import { Users, Followings } from '../../../../models'; import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query'; -import { generateBlockQueryForUsers } from '../../common/generate-block-query'; +import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query'; export const meta = { tags: ['users'], @@ -46,6 +46,7 @@ export default define(meta, async (ps, me) => { generateMutedUserQueryForUsers(query, me); generateBlockQueryForUsers(query, me); + generateBlockedUserQuery(query, me); const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts index 9b7c31e7b..2824d7d1b 100644 --- a/src/server/api/stream/channel.ts +++ b/src/server/api/stream/channel.ts @@ -27,6 +27,10 @@ export default abstract class Channel { return this.connection.muting; } + protected get blocking() { + return this.connection.blocking; + } + protected get followingChannels() { return this.connection.followingChannels; } diff --git a/src/server/api/stream/channels/antenna.ts b/src/server/api/stream/channels/antenna.ts index d93143f4d..db4fab841 100644 --- a/src/server/api/stream/channels/antenna.ts +++ b/src/server/api/stream/channels/antenna.ts @@ -2,6 +2,7 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; import { Notes } from '../../../../models'; import { isMutedUserRelated } from '@/misc/is-muted-user-related'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'antenna'; @@ -26,6 +27,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; this.connection.cacheNote(note); diff --git a/src/server/api/stream/channels/channel.ts b/src/server/api/stream/channels/channel.ts index 6af2d80e0..7910f0f2f 100644 --- a/src/server/api/stream/channels/channel.ts +++ b/src/server/api/stream/channels/channel.ts @@ -2,6 +2,7 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; import { Notes, Users } from '../../../../models'; import { isMutedUserRelated } from '@/misc/is-muted-user-related'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; import { PackedNote } from '../../../../models/repositories/note'; import { User } from '../../../../models/entities/user'; @@ -42,6 +43,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; this.connection.cacheNote(note); diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts index c59eb3777..02792bffa 100644 --- a/src/server/api/stream/channels/global-timeline.ts +++ b/src/server/api/stream/channels/global-timeline.ts @@ -5,6 +5,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { Notes } from '../../../../models'; import { PackedNote } from '../../../../models/repositories/note'; import { checkWordMute } from '@/misc/check-word-mute'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'globalTimeline'; @@ -49,6 +50,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; // 流れてきたNoteがミュートすべきNoteだったら無視する // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts index b662af072..4cabd4db6 100644 --- a/src/server/api/stream/channels/hashtag.ts +++ b/src/server/api/stream/channels/hashtag.ts @@ -4,6 +4,7 @@ import Channel from '../channel'; import { Notes } from '../../../../models'; import { PackedNote } from '../../../../models/repositories/note'; import { normalizeForSearch } from '@/misc/normalize-for-search'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'hashtag'; @@ -36,6 +37,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; this.connection.cacheNote(note); diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index b1091a46e..7659b5ffa 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -4,6 +4,7 @@ import Channel from '../channel'; import { Notes } from '../../../../models'; import { PackedNote } from '../../../../models/repositories/note'; import { checkWordMute } from '@/misc/check-word-mute'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'homeTimeline'; @@ -57,6 +58,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; // 流れてきたNoteがミュートすべきNoteだったら無視する // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index d769a2437..664435f67 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -6,6 +6,7 @@ import { Notes } from '../../../../models'; import { PackedNote } from '../../../../models/repositories/note'; import { PackedUser } from '../../../../models/repositories/user'; import { checkWordMute } from '@/misc/check-word-mute'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'hybridTimeline'; @@ -66,6 +67,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; // 流れてきたNoteがミュートすべきNoteだったら無視する // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts index aa0b6c402..528059dab 100644 --- a/src/server/api/stream/channels/local-timeline.ts +++ b/src/server/api/stream/channels/local-timeline.ts @@ -6,6 +6,7 @@ import { Notes } from '../../../../models'; import { PackedNote } from '../../../../models/repositories/note'; import { PackedUser } from '../../../../models/repositories/user'; import { checkWordMute } from '@/misc/check-word-mute'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'localTimeline'; @@ -51,6 +52,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; // 流れてきたNoteがミュートすべきNoteだったら無視する // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts index da227f24e..1f42fbe49 100644 --- a/src/server/api/stream/channels/user-list.ts +++ b/src/server/api/stream/channels/user-list.ts @@ -4,6 +4,7 @@ import { Notes, UserListJoinings, UserLists } from '../../../../models'; import { isMutedUserRelated } from '@/misc/is-muted-user-related'; import { User } from '../../../../models/entities/user'; import { PackedNote } from '../../../../models/repositories/note'; +import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; export default class extends Channel { public readonly chName = 'userList'; @@ -74,6 +75,8 @@ export default class extends Channel { // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isBlockerUserRelated(note, this.blocking)) return; this.send('note', note); } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 75d82cfe6..96d4194a7 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -8,7 +8,7 @@ import channels from './channels'; import { EventEmitter } from 'events'; import { User } from '../../../models/entities/user'; import { Channel as ChannelModel } from '../../../models/entities/channel'; -import { Users, Followings, Mutings, UserProfiles, ChannelFollowings } from '../../../models'; +import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '../../../models'; import { ApiError } from '../error'; import { AccessToken } from '../../../models/entities/access-token'; import { UserProfile } from '../../../models/entities/user-profile'; @@ -24,6 +24,7 @@ export default class Connection { public userProfile?: UserProfile; public following: Set = new Set(); public muting: Set = new Set(); + public blocking: Set = new Set(); // "被"blocking public followingChannels: Set = new Set(); public token?: AccessToken; private wsConnection: websocket.connection; @@ -52,6 +53,7 @@ export default class Connection { if (this.user) { this.updateFollowing(); this.updateMuting(); + this.updateBlocking(); this.updateFollowingChannels(); this.updateUserProfile(); @@ -80,6 +82,8 @@ export default class Connection { this.muting.delete(body.id); break; + // TODO: block events + case 'followChannel': this.followingChannels.add(body.id); break; @@ -375,6 +379,18 @@ export default class Connection { this.muting = new Set(mutings.map(x => x.muteeId)); } + @autobind + private async updateBlocking() { // ここでいうBlockingは被Blockingの意 + const blockings = await Blockings.find({ + where: { + blockeeId: this.user!.id + }, + select: ['blockerId'] + }); + + this.blocking = new Set(blockings.map(x => x.blockerId)); + } + @autobind private async updateFollowingChannels() { const followings = await ChannelFollowings.find({ diff --git a/src/services/blocking/create.ts b/src/services/blocking/create.ts index be2a62692..d92856689 100644 --- a/src/services/blocking/create.ts +++ b/src/services/blocking/create.ts @@ -6,7 +6,7 @@ import renderBlock from '../../remote/activitypub/renderer/block'; import { deliver } from '../../queue'; import renderReject from '../../remote/activitypub/renderer/reject'; import { User } from '../../models/entities/user'; -import { Blockings, Users, FollowRequests, Followings } from '../../models'; +import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '../../models'; import { perUserFollowingChart } from '../chart'; import { genId } from '@/misc/gen-id'; @@ -15,7 +15,8 @@ export default async function(blocker: User, blockee: User) { cancelRequest(blocker, blockee), cancelRequest(blockee, blocker), unFollow(blocker, blockee), - unFollow(blockee, blocker) + unFollow(blockee, blocker), + removeFromList(blockee, blocker), ]); await Blockings.insert({ @@ -112,3 +113,16 @@ async function unFollow(follower: User, followee: User) { deliver(follower, content, followee.inbox); } } + +async function removeFromList(listOwner: User, user: User) { + const userLists = await UserLists.find({ + userId: listOwner.id, + }); + + for (const userList of userLists) { + await UserListJoinings.delete({ + userListId: userList.id, + userId: user.id, + }); + } +} diff --git a/src/services/note/create.ts b/src/services/note/create.ts index b9b39d25e..13d612cf8 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -16,7 +16,7 @@ import { extractMentions } from '@/misc/extract-mentions'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm'; import { extractHashtags } from '@/misc/extract-hashtags'; import { Note, IMentionedRemoteUsers } from '../../models/entities/note'; -import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings } from '../../models'; +import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings } from '../../models'; import { DriveFile } from '../../models/entities/drive-file'; import { App } from '../../models/entities/app'; import { Not, getConnection, In } from 'typeorm'; @@ -265,8 +265,10 @@ export default async (user: { id: User['id']; username: User['username']; host: .andWhere(`following.followeeId = :userId`, { userId: note.userId }) .getMany() .then(async followings => { + const blockings = await Blockings.find({ blockerId: user.id }); // TODO: キャッシュしたい const followers = followings.map(f => f.followerId); for (const antenna of (await getAntennas())) { + if (blockings.some(blocking => blocking.blockeeId === antenna.userId)) continue; // この処理は checkHitAntenna 内でやるようにしてもいいかも checkHitAntenna(antenna, note, user, followers).then(hit => { if (hit) { addNoteToAntenna(antenna, note, user); diff --git a/src/services/note/polls/vote.ts b/src/services/note/polls/vote.ts index aea157e55..d3cf9f211 100644 --- a/src/services/note/polls/vote.ts +++ b/src/services/note/polls/vote.ts @@ -1,7 +1,7 @@ import { publishNoteStream } from '../../stream'; import { User } from '../../../models/entities/user'; import { Note } from '../../../models/entities/note'; -import { PollVotes, NoteWatchings, Polls } from '../../../models'; +import { PollVotes, NoteWatchings, Polls, Blockings } from '../../../models'; import { Not } from 'typeorm'; import { genId } from '@/misc/gen-id'; import { createNotification } from '../../create-notification'; @@ -14,6 +14,17 @@ export default async function(user: User, note: Note, choice: number) { // Check whether is valid choice if (poll.choices[choice] == null) throw new Error('invalid choice param'); + // Check blocking + if (note.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: note.userId, + blockeeId: user.id, + }); + if (block) { + throw new Error('blocked'); + } + } + // if already voted const exist = await PollVotes.find({ noteId: note.id, diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index ce6ae08b3..b8a8d172f 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -5,7 +5,7 @@ import { renderActivity } from '../../../remote/activitypub/renderer'; import { toDbReaction, decodeReaction } from '@/misc/reaction-lib'; import { User, IRemoteUser } from '../../../models/entities/user'; import { Note } from '../../../models/entities/note'; -import { NoteReactions, Users, NoteWatchings, Notes, Emojis } from '../../../models'; +import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '../../../models'; import { Not } from 'typeorm'; import { perUserReactionsChart } from '../../chart'; import { genId } from '@/misc/gen-id'; @@ -16,6 +16,17 @@ import { NoteReaction } from '../../../models/entities/note-reaction'; import { IdentifiableError } from '@/misc/identifiable-error'; export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { + // Check blocking + if (note.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: note.userId, + blockeeId: user.id, + }); + if (block) { + throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7'); + } + } + // TODO: cache reaction = await toDbReaction(reaction, user.host); diff --git a/test/block.ts b/test/block.ts new file mode 100644 index 000000000..408b218a7 --- /dev/null +++ b/test/block.ts @@ -0,0 +1,95 @@ +/* + * Tests of block + * + * How to run the tests: + * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/block.ts --require ts-node/register + * + * To specify test: + * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/block.ts --require ts-node/register -g 'test name' + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, startServer, shutdownServer } from './utils'; + +describe('Block', () => { + let p: childProcess.ChildProcess; + + // alice blocks bob + let alice: any; + let bob: any; + let carol: any; + + before(async () => { + p = await startServer(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + }); + + after(async () => { + await shutdownServer(p); + }); + + it('Block作成', async(async () => { + const res = await request('/blocking/create', { + userId: bob.id + }, alice); + + assert.strictEqual(res.status, 200); + })); + + it('ブロックされているユーザーをフォローできない', async(async () => { + const res = await request('/following/create', { userId: alice.id }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0'); + })); + + it('ブロックされているユーザーにリアクションできない', async(async () => { + const note = await post(alice, { text: 'hello' }); + + const res = await request('/notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec'); + })); + + it('ブロックされているユーザーに返信できない', async(async () => { + const note = await post(alice, { text: 'hello' }); + + const res = await request('/notes/create', { replyId: note.id, text: 'yo' }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); + })); + + it('ブロックされているユーザーのノートをRenoteできない', async(async () => { + const note = await post(alice, { text: 'hello' }); + + const res = await request('/notes/create', { renoteId: note.id, text: 'yo' }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); + })); + + // TODO: ユーザーリストに入れられないテスト + + // TODO: ユーザーリストから除外されるテスト + + it('タイムライン(LTL)にブロックされているユーザーの投稿が含まれない', async(async () => { + const aliceNote = await post(alice); + const bobNote = await post(bob); + const carolNote = await post(carol); + + const res = await request('/notes/local-timeline', {}, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); + })); +});