diff --git a/packages/backend/migration/1682891891317-AddHiddenPosts.js b/packages/backend/migration/1682891891317-AddHiddenPosts.js new file mode 100644 index 000000000..9fbb553b3 --- /dev/null +++ b/packages/backend/migration/1682891891317-AddHiddenPosts.js @@ -0,0 +1,13 @@ +export class AddHiddenPosts1682891891317 { + name = "AddHiddenPosts1682891891317"; + + async up(queryRunner) { + await queryRunner.query( + `ALTER TYPE note_visibility_enum ADD VALUE IF NOT EXISTS 'hidden'`, + ); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TYPE note_visibility_enum REMOVE VALUE IF EXISTS 'hidden'`); + } +} diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index 10449bb6d..f4e76c1db 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -111,6 +111,7 @@ export class Note { /** * public ... 公開 * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す + * hidden ... only visible on profile (doesnt federate, like local only, but can be fetched via AP like home) <- for now only used for post imports * followers ... フォロワーのみ * specified ... visibleUserIds で指定したユーザーのみ */ diff --git a/packages/backend/src/queue/processors/db/import-posts.ts b/packages/backend/src/queue/processors/db/import-posts.ts index 20ef3a518..a0a916b41 100644 --- a/packages/backend/src/queue/processors/db/import-posts.ts +++ b/packages/backend/src/queue/processors/db/import-posts.ts @@ -65,7 +65,7 @@ export async function importPosts( renote: null, cw: cw, localOnly, - visibility: "public", + visibility: "hidden", visibleUsers: [], channel: null, apMentions: new Array(0), @@ -109,7 +109,7 @@ export async function importPosts( renote: null, cw: post.sensitive, localOnly: false, - visibility: "public", + visibility: "hidden", visibleUsers: [], channel: null, apMentions: new Array(0), diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 7b41de670..719feb56f 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -686,7 +686,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) { multiple: poll?.multiple, votes: poll?.votes, expiresAt: poll?.expiresAt, - noteVisibility: note.visibility, + noteVisibility: note.visibility === "hidden" ? "home" : note.visibility, userId: actor.id, userHost: actor.host, }); @@ -704,7 +704,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) { multiple: poll?.multiple, votes: poll?.votes, expiresAt: poll?.expiresAt, - noteVisibility: note.visibility, + noteVisibility: note.visibility === "hidden" ? "home" : note.visibility, }, ); updating = true; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 077a1ad5e..78a193283 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -32,6 +32,11 @@ export const meta = { code: "GTL_DISABLED", id: "0332fc13-6ab2-4427-ae80-a9fadffd1a6b", }, + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, }, } as const; @@ -93,6 +98,7 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.withFiles) { query.andWhere("note.fileIds != '{}'"); } + query.andWhere("note.visibility != 'hidden'"); //#endregion process.nextTick(() => { @@ -106,11 +112,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 3c171278b..508b268cc 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -36,6 +36,11 @@ export const meta = { code: "STL_DISABLED", id: "620763f4-f621-4533-ab33-0577a1a3c342", }, + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, }, } as const; @@ -151,6 +156,8 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.withFiles) { query.andWhere("note.fileIds != '{}'"); } + + query.andWhere("note.visibility != 'hidden'"); //#endregion process.nextTick(() => { @@ -162,11 +169,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index cec371c8d..797c6d77c 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -35,6 +35,11 @@ export const meta = { code: "LTL_DISABLED", id: "45a6eb02-7695-4393-b023-dd3be9aaaefd", }, + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, }, } as const; @@ -123,6 +128,7 @@ export default define(meta, paramDef, async (ps, user) => { ); } } + query.andWhere("note.visibility != 'hidden'"); //#endregion process.nextTick(() => { @@ -136,11 +142,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts index 56847b1dd..321ab4ad7 100644 --- a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts @@ -35,6 +35,11 @@ export const meta = { code: "RTL_DISABLED", id: "45a6eb02-7695-4393-b023-dd3be9aaaefe", }, + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, }, } as const; @@ -126,6 +131,7 @@ export default define(meta, paramDef, async (ps, user) => { ); } } + query.andWhere("note.visibility != 'hidden'"); //#endregion process.nextTick(() => { @@ -139,11 +145,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index f85c0cfd3..62996efdd 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -10,6 +10,7 @@ import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.j import { generateChannelQuery } from "../../common/generate-channel-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js"; +import { ApiError } from "../../error.js"; export const meta = { tags: ["notes"], @@ -27,6 +28,14 @@ export const meta = { ref: "Note", }, }, + + errors: { + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, + }, } as const; export const paramDef = { @@ -143,6 +152,8 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.withFiles) { query.andWhere("note.fileIds != '{}'"); } + + query.andWhere("note.visibility != 'hidden'"); //#endregion process.nextTick(() => { @@ -154,11 +165,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 03f5cee3f..5c3fc55be 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -29,6 +29,11 @@ export const meta = { code: "NO_SUCH_LIST", id: "8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff", }, + queryError: { + message: "Please follow more users.", + code: "QUERY_ERROR", + id: "620763f4-f621-4533-ab33-0577a1a3c343", + }, }, } as const; @@ -149,11 +154,15 @@ export default define(meta, paramDef, async (ps, user) => { const found = []; const take = Math.floor(ps.limit * 1.5); let skip = 0; - while (found.length < ps.limit) { - const notes = await query.take(take).skip(skip).getMany(); - found.push(...(await Notes.packMany(notes, user))); - skip += take; - if (notes.length < take) break; + try { + while (found.length < ps.limit) { + const notes = await query.take(take).skip(skip).getMany(); + found.push(...(await Notes.packMany(notes, user))); + skip += take; + if (notes.length < take) break; + } + } catch (error) { + throw new ApiError(meta.errors.queryError); } if (found.length > ps.limit) { diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index dc291c515..75768ecca 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -29,6 +29,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; if (note.channelId !== this.channelId) return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index a2e5481ab..f1ba0a9dc 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -24,6 +24,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : []; 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 bb79d158c..dd36c60a1 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -20,6 +20,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; if (note.channelId) { if (!this.followingChannels.has(note.channelId)) return; } else { 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 c73eb4f70..d734f59df 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -29,6 +29,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または // チャンネルの投稿ではなく、全体公開のローカルの投稿 または diff --git a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts index cc30a85a3..d2e60f3b5 100644 --- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts @@ -29,6 +29,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または // チャンネルの投稿ではなく、全体公開のローカルの投稿 または 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 105c45955..c2b62c05a 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -49,6 +49,7 @@ export default class extends Channel { } private async onNote(note: Packed<"Note">) { + if (note.visibility === "hidden") return; if (!this.listUsers.includes(note.userId)) return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 277393eb4..4b9b20ec3 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -144,7 +144,7 @@ export default async ( }); //#region deliver - if (Users.isLocalUser(user) && !note.localOnly) { + if (Users.isLocalUser(user) && !note.localOnly && note.visibility !== "hidden") { const content = renderActivity(await renderLike(record, note)); const dm = new DeliverManager(user, content); if (note.userHost !== null) { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 9e53440a1..2ba6da2f7 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -18,6 +18,7 @@ export const noteVisibilities = [ "home", "followers", "specified", + "hidden", ] as const; export const mutedNoteReasons = ["word", "manual", "spam", "other"] as const;