diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index ddcfade42..96267400a 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -9,10 +9,6 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
return `(❌⛔)`;
}
- if (note.isHidden) {
- return `(⛔)`;
- }
-
let summary = '';
// 本文
diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts
index 9bd97f988..1d5702053 100644
--- a/packages/backend/src/models/repositories/note-favorite.ts
+++ b/packages/backend/src/models/repositories/note-favorite.ts
@@ -14,6 +14,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
id: favorite.id,
createdAt: favorite.createdAt.toISOString(),
noteId: favorite.noteId,
+ // may throw error
note: await Notes.pack(favorite.note || favorite.noteId, me),
};
},
@@ -22,6 +23,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
favorites: any[],
me: { id: User['id'] }
) {
- return Promise.all(favorites.map(x => this.pack(x, me)));
+ return Promise.allSettled(favorites.map(x => this.pack(x, me)))
+ .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []));
},
});
diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts
index 4deae51c9..46084a9a1 100644
--- a/packages/backend/src/models/repositories/note-reaction.ts
+++ b/packages/backend/src/models/repositories/note-reaction.ts
@@ -25,8 +25,22 @@ export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
user: await Users.pack(reaction.user ?? reaction.userId, me),
type: convertLegacyReaction(reaction.reaction),
...(opts.withNote ? {
+ // may throw error
note: await Notes.pack(reaction.note ?? reaction.noteId, me),
} : {}),
};
},
+
+ async packMany(
+ src: NoteReaction[],
+ me?: { id: User['id'] } | null | undefined,
+ options?: {
+ withNote: booleam;
+ },
+ ): Promise[]> {
+ const reactions = await Promise.allSettled(src.map(reaction => this.pack(reaction, me, options)));
+
+ // filter out rejected promises, only keep fulfilled values
+ return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []);
+ }
});
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index 3fefab031..e697b4cea 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -10,66 +10,7 @@ import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { db } from '@/db/postgre.js';
-
-async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
- // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
- let hide = false;
-
- // visibility が specified かつ自分が指定されていなかったら非表示
- if (packedNote.visibility === 'specified') {
- if (meId == null) {
- hide = true;
- } else if (meId === packedNote.userId) {
- hide = false;
- } else {
- // 指定されているかどうか
- const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
-
- if (specified) {
- hide = false;
- } else {
- hide = true;
- }
- }
- }
-
- // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
- if (packedNote.visibility === 'followers') {
- if (meId == null) {
- hide = true;
- } else if (meId === packedNote.userId) {
- hide = false;
- } else if (packedNote.reply && (meId === packedNote.reply.userId)) {
- // 自分の投稿に対するリプライ
- hide = false;
- } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
- // 自分へのメンション
- hide = false;
- } else {
- // フォロワーかどうか
- const following = await Followings.findOneBy({
- followeeId: packedNote.userId,
- followerId: meId,
- });
-
- if (following == null) {
- hide = true;
- } else {
- hide = false;
- }
- }
- }
-
- if (hide) {
- packedNote.visibleUserIds = undefined;
- packedNote.fileIds = [];
- packedNote.files = [];
- packedNote.text = null;
- packedNote.poll = undefined;
- packedNote.cw = null;
- packedNote.isHidden = true;
- }
-}
+import { IdentifiableError } from '@/misc/identifiable-error.js';
async function populatePoll(note: Note, meId: User['id'] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
@@ -193,7 +134,6 @@ export const NoteRepository = db.getRepository(Note).extend({
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
- skipHide?: boolean;
_hint_?: {
myReactions: Map;
};
@@ -201,13 +141,16 @@ export const NoteRepository = db.getRepository(Note).extend({
): Promise> {
const opts = Object.assign({
detail: true,
- skipHide: false,
}, options);
const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const host = note.userHost;
+ if (!await this.isVisibleForMe(note, meId)) {
+ throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
+ }
+
let text = note.text;
if (note.name && (note.url ?? note.uri)) {
@@ -282,10 +225,6 @@ export const NoteRepository = db.getRepository(Note).extend({
packed.text = mfm.toString(tokens);
}
- if (!opts.skipHide) {
- await hideNote(packed, meId);
- }
-
return packed;
},
@@ -294,7 +233,6 @@ export const NoteRepository = db.getRepository(Note).extend({
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
- skipHide?: boolean;
}
) {
if (notes.length === 0) return [];
@@ -316,11 +254,14 @@ export const NoteRepository = db.getRepository(Note).extend({
await prefetchEmojis(aggregateNoteEmojis(notes));
- return await Promise.all(notes.map(n => this.pack(n, me, {
+ const promises = await Promise.allSettled(notes.map(n => this.pack(n, me, {
...options,
_hint_: {
myReactions: myReactionsMap,
},
})));
+
+ // filter out rejected promises, only keep fulfilled values
+ return promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []);
},
});
diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts
index cdf4b9a54..292bbb82f 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/backend/src/models/schema/note.ts
@@ -52,10 +52,6 @@ export const packedNoteSchema = {
optional: true, nullable: true,
ref: 'Note',
},
- isHidden: {
- type: 'boolean',
- optional: true, nullable: false,
- },
visibility: {
type: 'string',
optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts
index 783ea9ef7..c5a1e765e 100644
--- a/packages/backend/src/server/api/common/getters.ts
+++ b/packages/backend/src/server/api/common/getters.ts
@@ -2,12 +2,20 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
import { User } from '@/models/entities/user.js';
import { Note } from '@/models/entities/note.js';
import { Notes, Users } from '@/models/index.js';
+import { generateVisibilityQuery } from './generate-visibility-query.js';
/**
- * Get note for API processing
+ * Get note for API processing, taking into account visibility.
*/
-export async function getNote(noteId: Note['id']) {
- const note = await Notes.findOneBy({ id: noteId });
+export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) {
+ const query = Notes.createQueryBuilder('note')
+ .where("note.id = :id", {
+ id: noteId,
+ });
+
+ generateVisibilityQuery(query, me);
+
+ const note = await query.getOne();
if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index 68a17867b..b5142fcf0 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -35,9 +35,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await PromoNotes.findOneBy({ noteId: note.id });
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index 5d72f5c1b..91baa8eb7 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -52,9 +52,9 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchClip);
}
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await ClipNotes.findOneBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index 5a4420a68..514386d73 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -39,9 +39,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, me).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const clipNotes = await ClipNotes.findBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index 28613962a..fa9b58848 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -41,9 +41,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const conversation: Note[] = [];
@@ -51,7 +51,11 @@ export default define(meta, paramDef, async (ps, user) => {
async function get(id: any) {
i++;
- const p = await Notes.findOneBy({ id });
+ const p = await getNote(id, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') return null;
+ throw e;
+ });
+
if (p == null) return;
if (i > ps.offset!) {
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index a13329416..d8954ec73 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -7,9 +7,11 @@ import { DriveFile } from '@/models/entities/drive-file.js';
import { Note } from '@/models/entities/note.js';
import { Channel } from '@/models/entities/channel.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
+import { HOUR } from '@/const.js';
import { noteVisibilities } from '../../../../types.js';
import { ApiError } from '../../error.js';
import define from '../../define.js';
+import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes'],
@@ -185,11 +187,12 @@ export default define(meta, paramDef, async (ps, user) => {
let renote: Note | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
- renote = await Notes.findOneBy({ id: ps.renoteId });
+ renote = await getNote(ps.renoteId, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchRenoteTarget);
+ throw e;
+ });
- if (renote == null) {
- throw new ApiError(meta.errors.noSuchRenoteTarget);
- } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
+ if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
throw new ApiError(meta.errors.cannotReRenote);
}
@@ -208,11 +211,12 @@ export default define(meta, paramDef, async (ps, user) => {
let reply: Note | null = null;
if (ps.replyId != null) {
// Fetch reply
- reply = await Notes.findOneBy({ id: ps.replyId });
+ reply = await getNote(ps.replyId, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchReplyTarget);
+ throw e;
+ });
- if (reply == null) {
- throw new ApiError(meta.errors.noSuchReplyTarget);
- } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
+ if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
}
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index c23ceeb5b..3169db43c 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -43,9 +43,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) {
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index 097371a42..b5dd88a4e 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -37,9 +37,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
// Get favoritee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
// if already favorited
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
index 82ef4fa19..3f4d39254 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
@@ -36,9 +36,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
// Get favoritee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
// if already favorited
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 45a832cbd..6dd5ddf9e 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -72,9 +72,9 @@ export default define(meta, paramDef, async (ps, user) => {
const createdAt = new Date();
// Get votee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
if (!note.hasPoll) {
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index be2846d25..00a89b3f2 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -3,6 +3,7 @@ import { NoteReactions } from '@/models/index.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
+import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes', 'reactions'],
@@ -47,6 +48,12 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
+ // check note visibility
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+
const query = {
noteId: ps.noteId,
} as FindOptionsWhere;
@@ -69,5 +76,5 @@ export default define(meta, paramDef, async (ps, user) => {
relations: ['user', 'user.avatar', 'user.banner', 'note'],
});
- return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user)));
+ return await NoteReactions.packMany(reactions, user);
});
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index 07e52a926..b5c0c9d17 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -42,9 +42,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await createReaction(user, note, ps.reaction).catch(e => {
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index c13cafa21..b8e234bb4 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -42,9 +42,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await deleteReaction(user, note).catch(e => {
if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted);
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index 4d0cd8fc6..2f662f355 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -45,9 +45,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index 470791b1b..83a39a855 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -34,12 +34,16 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
return await Notes.pack(note, user, {
+ // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
detail: true,
+ }).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
});
diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts
index 01afa5add..67579b2a6 100644
--- a/packages/backend/src/server/api/endpoints/notes/state.ts
+++ b/packages/backend/src/server/api/endpoints/notes/state.ts
@@ -1,4 +1,5 @@
import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
+import { getNote } from '../../common/getters.js';
import define from '../../define.js';
export const meta = {
@@ -36,7 +37,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await Notes.findOneByOrFail({ id: ps.noteId });
+ const note = await getNote(ps.noteId, user);
const [favorite, watching, threadMuting] = await Promise.all([
NoteFavorites.count({
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index cf360526d..4154b5dc5 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -31,9 +31,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const mutedNotes = await Notes.find({
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
index ac310d0fe..cbc0e5ce5 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await NoteThreadMutings.delete({
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index ba6e262d6..a01dcfa48 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -39,15 +39,11 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
- if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) {
- return 204; // TODO: 良い感じのエラー返す
- }
-
if (note.text == null) {
return 204;
}
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 3fba0efe0..400eefb70 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -37,9 +37,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const renotes = await Notes.findBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
index 7d482b073..6025799fa 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await watch(user.id, note);
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
index 2c1a2e5fb..7021c7970 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await unwatch(user.id, note);
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index c6a940c65..7c37fcbf7 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -28,9 +28,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await PromoReads.findOneBy({
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 79cf58a41..144326958 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -63,5 +63,5 @@ export default define(meta, paramDef, async (ps, me) => {
.take(ps.limit)
.getMany();
- return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
+ return await NoteReactions.packMany(reactions, me, { withNote: true });
});
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index d2cc5122d..c9cffd2d3 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -1,4 +1,8 @@
import Connection from '.';
+import { Note } from '@/models/entities/note.js';
+import { Notes } from '@/models/index.js';
+import { Packed } from '@/misc/schema.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
/**
* Stream channel
@@ -54,6 +58,32 @@ export default abstract class Channel {
});
}
+ protected withPackedNote(callback: (note: Packed<'Note'>) => void): (Note) => void {
+ return async (note: Note) => {
+ try {
+ // because `note` was previously JSON.stringify'ed, the fields that
+ // were objects before are now strings and have to be restored or
+ // removed from the object
+ note.createdAt = new Date(note.createdAt);
+ delete note.reply;
+ delete note.renote;
+ delete note.user;
+ delete note.channel;
+
+ const packed = await Notes.pack(note, this.user, { detail: true });
+
+ callback(packed);
+ } catch (err) {
+ if (err instanceof IdentifiableError && err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw err;
+ }
+ }
+ };
+ }
+
public abstract init(params: any): void;
public dispose?(): void;
public onMessage?(type: string, body: any): void;
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index d28320d92..a9a98e904 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -2,6 +2,7 @@ import Channel from '../channel.js';
import { Notes } from '@/models/index.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { StreamMessages } from '../types.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
export default class extends Channel {
public readonly chName = 'antenna';
@@ -23,16 +24,25 @@ export default class extends Channel {
private async onEvent(data: StreamMessages['antenna']['payload']) {
if (data.type === 'note') {
- const note = await Notes.pack(data.body.id, this.user, { detail: true });
+ try {
+ const note = await Notes.pack(data.body.id, this.user, { detail: true });
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
- // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
- this.connection.cacheNote(note);
+ this.connection.cacheNote(note);
- this.send('note', note);
+ this.send('note', note);
+ } catch (e) {
+ if (e instanceof IdentifiableError && e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw e;
+ }
+ }
} else {
this.send(data.type, data.body);
}
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 3cdd89a8b..7ed47c389 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -1,5 +1,5 @@
import Channel from '../channel.js';
-import { Notes, Users } from '@/models/index.js';
+import { Users } from '@/models/index.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { User } from '@/models/entities/user.js';
import { StreamMessages } from '../types.js';
@@ -31,19 +31,6 @@ export default class extends Channel {
private async onNote(note: Packed<'Note'>) {
if (note.channelId !== this.channelId) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index 5b4ae850e..391851ecd 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { isUserRelated } from '@/misc/is-user-related.js';
@@ -13,7 +12,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -30,19 +29,6 @@ export default class extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 741db447e..f9f7ae410 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -29,13 +28,6 @@ export default class extends Channel {
const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag))));
if (!matched) return;
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 075a242ef..9f5188547 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -31,29 +30,6 @@ export default class extends Channel {
// Ignore notes from instances the user has muted
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user!, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user!, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user!, {
- detail: true,
- });
- }
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index f5dedf77c..e73136b8e 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
@@ -13,7 +12,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -36,29 +35,6 @@ export default class extends Channel {
(note.channelId != null && this.followingChannels.has(note.channelId))
)) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user!, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user!, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user!, {
- detail: true,
- });
- }
- }
-
// Ignore notes from instances the user has muted
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index f01f47723..729de6d4a 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -30,19 +29,6 @@ export default class extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index 9cfea0bfc..7f42263db 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
export default class extends Channel {
@@ -16,26 +15,12 @@ export default class extends Channel {
if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
if (data.body.userId && this.muting.has(data.body.userId)) return;
- if (data.body.note && data.body.note.isHidden) {
- const note = await Notes.pack(data.body.note.id, this.user, {
- detail: true,
- });
- this.connection.cacheNote(note);
- data.body.note = note;
- }
break;
}
case 'mention': {
if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
if (this.muting.has(data.body.userId)) return;
- if (data.body.isHidden) {
- const note = await Notes.pack(data.body.id, this.user, {
- detail: true,
- });
- this.connection.cacheNote(note);
- data.body = note;
- }
break;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index 97ad2983c..9b2476148 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -1,5 +1,5 @@
import Channel from '../channel.js';
-import { Notes, UserListJoinings, UserLists } from '@/models/index.js';
+import { UserListJoinings, UserLists } from '@/models/index.js';
import { User } from '@/models/entities/user.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -15,7 +15,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
this.updateListUsers = this.updateListUsers.bind(this);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -51,29 +51,6 @@ export default class extends Channel {
private async onNote(note: Packed<'Note'>) {
if (!this.listUsers.includes(note.userId)) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index 3b0a75d79..8050d8a1d 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -243,7 +243,7 @@ export type StreamMessages = {
};
notes: {
name: 'notesStream';
- payload: Packed<'Note'>;
+ payload: Note;
};
};
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index f26a5ee71..dab80814f 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -344,24 +344,32 @@ router.get('/notes/:note', async (ctx, next) => {
});
if (note) {
- const _note = await Notes.pack(note);
- const profile = await UserProfiles.findOneByOrFail({ userId: note.userId });
- const meta = await fetchMeta();
- await ctx.render('note', {
- note: _note,
- profile,
- avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
- // TODO: Let locale changeable by instance setting
- summary: getNoteSummary(_note),
- instanceName: meta.name || 'Calckey',
- icon: meta.iconUrl,
- privateMode: meta.privateMode,
- themeColor: meta.themeColor,
- });
+ try {
+ // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
+ const _note = await Notes.pack(note);
+ const profile = await UserProfiles.findOneByOrFail({ userId: note.userId });
+ const meta = await fetchMeta();
+ await ctx.render('note', {
+ note: _note,
+ profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
+ // TODO: Let locale changeable by instance setting
+ summary: getNoteSummary(_note),
+ instanceName: meta.name || 'Misskey',
+ icon: meta.iconUrl,
+ themeColor: meta.themeColor,
+ });
- ctx.set('Cache-Control', 'public, max-age=15');
+ ctx.set('Cache-Control', 'public, max-age=15');
- return;
+ return;
+ } catch (err) {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // note not visible to user
+ } else {
+ throw err;
+ }
+ }
}
await next();
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index e2bf9d5b5..3d5b308f3 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -345,19 +345,15 @@ export default async (user: { id: User['id']; username: User['username']; host:
}
}
- // Pack the note
- const noteObj = await Notes.pack(note);
+ publishNotesStream(note);
- publishNotesStream(noteObj);
+ const webhooks = await getActiveWebhooks().then(webhooks => webhooks.filter(x => x.userId === user.id && x.on.includes('note')));
- getActiveWebhooks().then(webhooks => {
- webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
- for (const webhook of webhooks) {
- webhookDeliver(webhook, 'note', {
- note: noteObj,
- });
- }
- });
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'note', {
+ note: await Notes.pack(note, user),
+ });
+ }
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];
@@ -378,12 +374,14 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (!threadMuted) {
nm.push(data.reply.userId, 'reply');
- publishMainStream(data.reply.userId, 'reply', noteObj);
+
+ const packedReply = await Notes.pack(note, { id: data.reply.userId });
+ publishMainStream(data.reply.userId, 'reply', packedReply);
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
for (const webhook of webhooks) {
webhookDeliver(webhook, 'reply', {
- note: noteObj,
+ note: packedReply,
});
}
}
@@ -404,12 +402,13 @@ export default async (user: { id: User['id']; username: User['username']; host:
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
- publishMainStream(data.renote.userId, 'renote', noteObj);
+ const packedRenote = await Notes.pack(note, { id: data.renote.userId });
+ publishMainStream(data.renote.userId, 'renote', packedRenote);
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
for (const webhook of webhooks) {
webhookDeliver(webhook, 'renote', {
- note: noteObj,
+ note: packedRenote,
});
}
}
@@ -642,17 +641,23 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note,
continue;
}
- const detailPackedNote = await Notes.pack(note, u, {
- detail: true,
- });
-
- publishMainStream(u.id, 'mention', detailPackedNote);
-
- const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
- for (const webhook of webhooks) {
- webhookDeliver(webhook, 'mention', {
- note: detailPackedNote,
+ // note with "specified" visibility might not be visible to mentioned users
+ try {
+ const detailPackedNote = await Notes.pack(note, u, {
+ detail: true,
});
+
+ publishMainStream(u.id, 'mention', detailPackedNote);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'mention', {
+ note: detailPackedNote,
+ });
+ }
+ } catch (err) {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') continue;
+ throw err;
}
// Create notification
diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts
index 9fa2b9713..4895bbace 100644
--- a/packages/backend/src/services/stream.ts
+++ b/packages/backend/src/services/stream.ts
@@ -22,7 +22,6 @@ import {
UserListStreamTypes,
UserStreamTypes,
} from '@/server/api/stream/types.js';
-import { Packed } from '@/misc/schema.js';
class Publisher {
private publish = (channel: StreamChannels, type: string | null, value?: any): void => {
@@ -87,7 +86,7 @@ class Publisher {
this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
};
- public publishNotesStream = (note: Packed<'Note'>): void => {
+ public publishNotesStream = (note: Note): void => {
this.publish('notesStream', null, note);
};
diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts
index b155549f9..cde3cd2d0 100644
--- a/packages/backend/test/api-visibility.ts
+++ b/packages/backend/test/api-visibility.ts
@@ -154,18 +154,18 @@ describe('API visibility', () => {
it('[show] followers-postを非フォロワーが見れない', async(async () => {
const res = await show(fol.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-postを未認証が見れない', async(async () => {
const res = await show(fol.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
it('[show] specified-postを自分が見れる', async(async () => {
const res = await show(spe.id, alice);
- assert.strictEqual(res.body.text, 'x');
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを指定ユーザーが見れる', async(async () => {
@@ -175,17 +175,17 @@ describe('API visibility', () => {
it('[show] specified-postをフォロワーが見れない', async(async () => {
const res = await show(spe.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを非フォロワーが見れない', async(async () => {
const res = await show(spe.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを未認証が見れない', async(async () => {
const res = await show(spe.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
@@ -260,12 +260,12 @@ describe('API visibility', () => {
it('[show] followers-replyを非フォロワーが見れない', async(async () => {
const res = await show(folR.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-replyを未認証が見れない', async(async () => {
const res = await show(folR.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
@@ -286,17 +286,17 @@ describe('API visibility', () => {
it('[show] specified-replyをフォロワーが見れない', async(async () => {
const res = await show(speR.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-replyを非フォロワーが見れない', async(async () => {
const res = await show(speR.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-replyを未認証が見れない', async(async () => {
const res = await show(speR.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
@@ -371,12 +371,12 @@ describe('API visibility', () => {
it('[show] followers-mentionを非フォロワーが見れない', async(async () => {
const res = await show(folM.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-mentionを未認証が見れない', async(async () => {
const res = await show(folM.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
@@ -392,22 +392,22 @@ describe('API visibility', () => {
it('[show] specified-mentionをされた人が指定されてなかったら見れない', async(async () => {
const res = await show(speM.id, target2);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionをフォロワーが見れない', async(async () => {
const res = await show(speM.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionを非フォロワーが見れない', async(async () => {
const res = await show(speM.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionを未認証が見れない', async(async () => {
const res = await show(speM.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue
index 650e130ed..025c7abdd 100644
--- a/packages/client/src/components/note-detailed.vue
+++ b/packages/client/src/components/note-detailed.vue
@@ -54,7 +54,6 @@
-
({{ i18n.ts.private }})
RN:
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 3c9d36170..cab5c8258 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -43,7 +43,6 @@
-
({{ i18n.ts.private }})
RN:
diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue
index 25ab883f4..9ee180345 100644
--- a/packages/client/src/components/sub-note-content.vue
+++ b/packages/client/src/components/sub-note-content.vue
@@ -1,7 +1,6 @@
- ({{ i18n.ts.private }})
({{ i18n.ts.deleted }})
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index d57e1c302..348351206 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -10,10 +10,6 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
return `(${i18n.ts.deletedNote})`;
}
- if (note.isHidden) {
- return `(${i18n.ts.invisibleNote})`;
- }
-
let summary = '';
// 本文