From cbfb898f2a54a6a9d5bd932c4fa2be3484130b42 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 13 Jun 2024 20:13:02 +0900 Subject: [PATCH 1/7] VRTL available for nodeinfo (#29) * feat: add VRTL-related metadata to nodeinfo * docs(changelog): add VRTL related fields --- CHANGELOG-VRTL.md | 5 +++++ packages/backend/src/server/NodeinfoServerService.ts | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 CHANGELOG-VRTL.md diff --git a/CHANGELOG-VRTL.md b/CHANGELOG-VRTL.md new file mode 100644 index 000000000000..6a97758970be --- /dev/null +++ b/CHANGELOG-VRTL.md @@ -0,0 +1,5 @@ +# CHANGELOG about VRTL + +This file lists changes to VRTL Generic Branch. + +- Add `vmimiRelayTimelineImplemented` and `disableVmimiRelayTimeline` to nodeinfo diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index cc18997fdc1c..000d7f0ba4f4 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -115,6 +115,8 @@ export class NodeinfoServerService { disableRegistration: meta.disableRegistration, disableLocalTimeline: !basePolicies.ltlAvailable, disableGlobalTimeline: !basePolicies.gtlAvailable, + vmimiRelayTimelineImplemented: true, + disableVmimiRelayTimeline: !basePolicies.vrtlAvailable, emailRequiredForSignup: meta.emailRequiredForSignup, enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, From aa00eb8c6c564447ffd1a0fefc656214bfdb051f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 14 Jun 2024 09:26:46 +0900 Subject: [PATCH 2/7] Fix replies to me are not included in V[RS]TL if withReplies is disabled (#31) * fix: reply to me is not shown on V[RS]TL * docs(changelog): Fix replies to me are not included in V[RS]TL if withReplies is disabled --- CHANGELOG-VRTL.md | 1 + packages/backend/src/core/FanoutTimelineService.ts | 1 + packages/backend/src/core/NoteCreateService.ts | 3 +++ .../server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts | 1 + .../src/server/api/endpoints/notes/vmimi-relay-timeline.ts | 1 + 5 files changed, 7 insertions(+) diff --git a/CHANGELOG-VRTL.md b/CHANGELOG-VRTL.md index 6a97758970be..2446537710c3 100644 --- a/CHANGELOG-VRTL.md +++ b/CHANGELOG-VRTL.md @@ -2,4 +2,5 @@ This file lists changes to VRTL Generic Branch. +- Fix replies to me are not included in V\[RS]TL if withReplies is disabled. - Add `vmimiRelayTimelineImplemented` and `disableVmimiRelayTimeline` to nodeinfo diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts index 4edd4cf2a157..be3c968eeb67 100644 --- a/packages/backend/src/core/FanoutTimelineService.ts +++ b/packages/backend/src/core/FanoutTimelineService.ts @@ -42,6 +42,7 @@ export type FanoutTimelineName = | 'vmimiRelayTimeline' // replies are not included | 'vmimiRelayTimelineWithFiles' // only non-reply notes with files are included | 'vmimiRelayTimelineWithReplies' // only replies are included + | `vmimiRelayTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id. @Injectable() export class FanoutTimelineService { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 0e9ddd0d6088..27b66167a382 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -955,6 +955,9 @@ export class NoteCreateService implements OnApplicationShutdown { } if (note.visibility === 'public' && this.vmimiRelayTimelineService.isRelayedInstance(note.userHost)) { this.fanoutTimelineService.push('vmimiRelayTimelineWithReplies', note.id, meta.vmimiRelayTimelineCacheMax, r); + if (note.replyUserHost == null) { + this.fanoutTimelineService.push(`vmimiRelayTimelineWithReplyTo:${note.replyUserId}`, note.id, meta.vmimiRelayTimelineCacheMax / 10, r); + } } } else { this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); diff --git a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts index 027e16a3470f..483bca4d2ecd 100644 --- a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts @@ -132,6 +132,7 @@ export default class extends Endpoint { // eslint- timelineConfig = [ `homeTimeline:${me.id}`, 'vmimiRelayTimeline', + `vmimiRelayTimelineWithReplyTo:${me.id}`, ]; } diff --git a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts index 032eaf06f714..4dc1e3b78100 100644 --- a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts @@ -119,6 +119,7 @@ export default class extends Endpoint { // eslint- redisTimelines: ps.withFiles ? ['vmimiRelayTimelineWithFiles'] : ps.withReplies ? ['vmimiRelayTimeline', 'vmimiRelayTimelineWithReplies'] + : me ? ['vmimiRelayTimeline', `vmimiRelayTimelineWithReplyTo:${me.id}`] : ['vmimiRelayTimeline'], alwaysIncludeMyNotes: true, excludePureRenotes: !ps.withRenotes, From 843152d0791fd8ecd92ee0ecaab7855ad70de2b4 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 16 Jun 2024 20:52:24 +0900 Subject: [PATCH 3/7] fix: Fix VRTL/VSTL is not avaiable on timeline widgets (#33) --- CHANGELOG-VRTL.md | 2 ++ packages/frontend/src/widgets/WidgetTimeline.vue | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-VRTL.md b/CHANGELOG-VRTL.md index 2446537710c3..03b56d6cf732 100644 --- a/CHANGELOG-VRTL.md +++ b/CHANGELOG-VRTL.md @@ -2,5 +2,7 @@ This file lists changes to VRTL Generic Branch. + +- Fix VRTL/VSTL is not avaiable on timeline widgets - Fix replies to me are not included in V\[RS]TL if withReplies is disabled. - Add `vmimiRelayTimelineImplemented` and `disableVmimiRelayTimeline` to nodeinfo diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 150e83858240..5b10f7827c98 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -10,17 +10,19 @@ SPDX-License-Identifier: AGPL-3.0-only + + -
+

{{ i18n.ts._disabledTimeline.title }} @@ -48,6 +50,7 @@ import { instance } from '@/instance.js'; const name = 'timeline'; const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable)); const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable)); +const isVmimiRelayTimelineAvailable = (($i == null && instance.policies.vrtlAvailable) || ($i != null && $i.policies.vrtlAvailable)); const widgetPropsDef = { showHeader: { @@ -131,6 +134,14 @@ const choose = async (ev) => { text: i18n.ts._timelines.global, icon: 'ti ti-whirl', action: () => { setSrc('global'); }, + }, { + text: i18n.ts._timelines.vmimiRelay, + icon: 'ti ti-whirl', + action: () => { setSrc('vmimi-relay'); }, + }, { + text: i18n.ts._timelines.vmimiRelaySocial, + icon: 'ti ti-whirl', + action: () => { setSrc('vmimi-relay-social'); }, }, antennaItems.length > 0 ? { type: 'divider' } : undefined, ...antennaItems, listItems.length > 0 ? { type: 'divider' } : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { menuOpened.value = false; }); From c947c192c3f0e4966a21b56734b1ea933748b445 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 16 Jun 2024 21:21:53 +0900 Subject: [PATCH 4/7] =?UTF-8?q?CHANGELOG=E3=82=92=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E=E3=81=AB=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG-VRTL.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG-VRTL.md b/CHANGELOG-VRTL.md index 03b56d6cf732..7d5bca9d614f 100644 --- a/CHANGELOG-VRTL.md +++ b/CHANGELOG-VRTL.md @@ -1,8 +1,9 @@ # CHANGELOG about VRTL -This file lists changes to VRTL Generic Branch. +VRTLのブランチで行われた変更点をまとめています -- Fix VRTL/VSTL is not avaiable on timeline widgets -- Fix replies to me are not included in V\[RS]TL if withReplies is disabled. -- Add `vmimiRelayTimelineImplemented` and `disableVmimiRelayTimeline` to nodeinfo +- fix(frontend): ウィジェットでVRTL/VSTLが使用できない問題を修正 +- fix(backend): 自分自身に対するリプライがwithReplies = falseなVRTL/VSTLにて含まれていない問題を修正 +- feat(backend): `vmimiRelayTimelineImplemented` と `disableVmimiRelayTimeline` nodeinfo に追加しました + - これによりサードパーティクライアントがVRTLの有無を認知できるようになりました。 From 24be6b1578b03674b2c7f9f11da3b18b1d8792e5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 16 Jun 2024 22:09:28 +0900 Subject: [PATCH 5/7] =?UTF-8?q?VRTL/VSTL=E3=81=AB=E9=80=A3=E5=90=88?= =?UTF-8?q?=E3=81=AA=E3=81=97=E6=8A=95=E7=A8=BF=E3=82=92=E5=90=AB=E3=82=81?= =?UTF-8?q?=E3=82=8B=E3=81=8B=E3=82=92=E9=81=B8=E6=8A=9E=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E3=81=AB=20(#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(backend): withLocalOnly to V[RS]TL * feat(frontend): TLに連合なし投稿を含めるかを選択可能に * docs(changelog): feat: VRTL/VSTLに連合なし投稿を含めるかを選択可能に --- CHANGELOG-VRTL.md | 3 +++ locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + packages/backend/src/core/NoteCreateService.ts | 4 ++-- .../notes/vmimi-relay-hybrid-timeline.ts | 9 +++++++++ .../api/endpoints/notes/vmimi-relay-timeline.ts | 16 ++++++++++++---- .../channels/vmimi-relay-hybrid-timeline.ts | 4 +++- .../api/stream/channels/vmimi-relay-timeline.ts | 3 +++ packages/frontend/src/components/MkTimeline.vue | 7 +++++++ packages/frontend/src/pages/timeline.vue | 13 +++++++++++-- packages/frontend/src/store.ts | 1 + packages/frontend/src/ui/deck/deck-store.ts | 1 + packages/frontend/src/ui/deck/tl-column.vue | 16 ++++++++++++++-- packages/misskey-js/etc/misskey-js.api.md | 2 ++ packages/misskey-js/src/autogen/types.ts | 4 ++++ packages/misskey-js/src/streaming.types.ts | 2 ++ 16 files changed, 79 insertions(+), 11 deletions(-) diff --git a/CHANGELOG-VRTL.md b/CHANGELOG-VRTL.md index 7d5bca9d614f..7c3dc085ce6a 100644 --- a/CHANGELOG-VRTL.md +++ b/CHANGELOG-VRTL.md @@ -3,6 +3,9 @@ VRTLのブランチで行われた変更点をまとめています +- feat: VRTL/VSTLに連合なし投稿を含めるかを選択可能に + - もともとのVRTL/VSTLでは連合なし投稿が常に含まれていましたが、正しくVRTL/VSTLのノートを表現するために含めないようにできるようになりました + - VSTLの場合、連合なし投稿を含めないようにしてもフォローしている人の連合なし投稿は表示されます - fix(frontend): ウィジェットでVRTL/VSTLが使用できない問題を修正 - fix(backend): 自分自身に対するリプライがwithReplies = falseなVRTL/VSTLにて含まれていない問題を修正 - feat(backend): `vmimiRelayTimelineImplemented` と `disableVmimiRelayTimeline` nodeinfo に追加しました diff --git a/locales/index.d.ts b/locales/index.d.ts index 28ab21630a4e..71c0927dabfe 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4716,6 +4716,10 @@ export interface Locale extends ILocale { * TLに現在フォロー中の人全員の返信を含めないようにする */ "hideRepliesToOthersInTimelineAll": string; + /** + * TLに連合なし投稿を含める + */ + "showLocalOnlyInTimeline": string; /** * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか? */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 699585d6fcf6..1450a42e63b9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1175,6 +1175,7 @@ showRepliesToOthersInTimeline: "TLに他の人への返信を含める" hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない" showRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返信を含めるようにする" hideRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返信を含めないようにする" +showLocalOnlyInTimeline: "TLに連合なし投稿を含める" confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか?" confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか?" externalServices: "外部サービス" diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 27b66167a382..e0a9e7c1badc 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -953,7 +953,7 @@ export class NoteCreateService implements OnApplicationShutdown { this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r); } } - if (note.visibility === 'public' && this.vmimiRelayTimelineService.isRelayedInstance(note.userHost)) { + if (note.visibility === 'public' && this.vmimiRelayTimelineService.isRelayedInstance(note.userHost) && !note.localOnly) { this.fanoutTimelineService.push('vmimiRelayTimelineWithReplies', note.id, meta.vmimiRelayTimelineCacheMax, r); if (note.replyUserHost == null) { this.fanoutTimelineService.push(`vmimiRelayTimelineWithReplyTo:${note.replyUserId}`, note.id, meta.vmimiRelayTimelineCacheMax / 10, r); @@ -971,7 +971,7 @@ export class NoteCreateService implements OnApplicationShutdown { this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r); } } - if (note.visibility === 'public' && this.vmimiRelayTimelineService.isRelayedInstance(note.userHost)) { + if (note.visibility === 'public' && this.vmimiRelayTimelineService.isRelayedInstance(note.userHost) && !note.localOnly) { this.fanoutTimelineService.push('vmimiRelayTimeline', note.id, meta.vmimiRelayTimelineCacheMax, r); if (note.fileIds.length > 0) { this.fanoutTimelineService.push('vmimiRelayTimelineWithFiles', note.id, meta.vmimiRelayTimelineCacheMax / 2, r); diff --git a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts index 483bca4d2ecd..f798a05e0c6d 100644 --- a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-hybrid-timeline.ts @@ -57,6 +57,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, withReplies: { type: 'boolean', default: false }, + withLocalOnly: { type: 'boolean', default: true }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, allowPartial: { type: 'boolean', default: true }, // this timeline is new so true by default sinceId: { type: 'string', format: 'misskey:id' }, @@ -106,6 +107,7 @@ export default class extends Endpoint { // eslint- limit: ps.limit, withFiles: ps.withFiles, withReplies: ps.withReplies, + withLocalOnly: ps.withLocalOnly, }, me); process.nextTick(() => { @@ -122,18 +124,21 @@ export default class extends Endpoint { // eslint- `homeTimelineWithFiles:${me.id}`, 'vmimiRelayTimelineWithFiles', ]; + if (ps.withLocalOnly) timelineConfig = [...timelineConfig, 'localTimelineWithFiles']; } else if (ps.withReplies) { timelineConfig = [ `homeTimeline:${me.id}`, 'vmimiRelayTimeline', 'vmimiRelayTimelineWithReplies', ]; + if (ps.withLocalOnly) timelineConfig = [...timelineConfig, 'localTimeline', 'localTimelineWithReplies']; } else { timelineConfig = [ `homeTimeline:${me.id}`, 'vmimiRelayTimeline', `vmimiRelayTimelineWithReplyTo:${me.id}`, ]; + if (ps.withLocalOnly) timelineConfig = [...timelineConfig, 'localTimeline', `localTimelineWithReplyTo:${me.id}`]; } const redisTimeline = await this.fanoutTimelineEndpointService.timeline({ @@ -152,6 +157,7 @@ export default class extends Endpoint { // eslint- limit, withFiles: ps.withFiles, withReplies: ps.withReplies, + withLocalOnly: ps.withLocalOnly, }, me), }); @@ -169,6 +175,7 @@ export default class extends Endpoint { // eslint- limit: number, withFiles: boolean, withReplies: boolean, + withLocalOnly: boolean, }, me: MiLocalUser) { const followees = await this.userFollowingService.getFollowees(me.id); const followingChannels = await this.channelFollowingsRepository.find({ @@ -185,6 +192,7 @@ export default class extends Endpoint { // eslint- qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); qb.orWhere(new Brackets(qb => { qb.where('note.visibility = \'public\''); + if (!ps.withLocalOnly) qb.andWhere('note.localOnly = FALSE'); qb.andWhere(new Brackets(qb => { qb.where('note.userHost IS NULL'); if (vmimiRelayInstances.length !== 0) { @@ -196,6 +204,7 @@ export default class extends Endpoint { // eslint- qb.where('note.userId = :meId', { meId: me.id }); qb.orWhere(new Brackets(qb => { qb.where('note.visibility = \'public\''); + if (!ps.withLocalOnly) qb.andWhere('note.localOnly = FALSE'); qb.andWhere(new Brackets(qb => { qb.where('note.userHost IS NULL'); if (vmimiRelayInstances.length !== 0) { diff --git a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts index 4dc1e3b78100..fddf282f7a64 100644 --- a/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/vmimi-relay-timeline.ts @@ -52,6 +52,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, withReplies: { type: 'boolean', default: false }, + withLocalOnly: { type: 'boolean', default: true }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, allowPartial: { type: 'boolean', default: true }, // this timeline is new so true by default sinceId: { type: 'string', format: 'misskey:id' }, @@ -98,6 +99,7 @@ export default class extends Endpoint { // eslint- withFiles: ps.withFiles, withRenotes: ps.withRenotes, withReplies: ps.withReplies, + withLocalOnly: ps.withLocalOnly, }, me); process.nextTick(() => { @@ -117,10 +119,10 @@ export default class extends Endpoint { // eslint- me, useDbFallback: serverSettings.enableFanoutTimelineDbFallback, redisTimelines: - ps.withFiles ? ['vmimiRelayTimelineWithFiles'] - : ps.withReplies ? ['vmimiRelayTimeline', 'vmimiRelayTimelineWithReplies'] - : me ? ['vmimiRelayTimeline', `vmimiRelayTimelineWithReplyTo:${me.id}`] - : ['vmimiRelayTimeline'], + ps.withFiles ? ['vmimiRelayTimelineWithFiles', ...(ps.withLocalOnly ? ['localTimelineWithFiles'] as const : [])] + : ps.withReplies ? ['vmimiRelayTimeline', 'vmimiRelayTimelineWithReplies', ...(ps.withLocalOnly ? ['localTimeline', 'localTimelineWithReplies'] as const : [])] + : me ? ['vmimiRelayTimeline', `vmimiRelayTimelineWithReplyTo:${me.id}`, ...(ps.withLocalOnly ? ['localTimeline', `localTimelineWithReplyTo:${me.id}`] as const : [])] + : ['vmimiRelayTimeline', ...(ps.withLocalOnly ? ['localTimeline'] as const : [])], alwaysIncludeMyNotes: true, excludePureRenotes: !ps.withRenotes, dbFallback: async (untilId, sinceId, limit) => await this.getFromDb({ @@ -130,6 +132,7 @@ export default class extends Endpoint { // eslint- withFiles: ps.withFiles, withRenotes: ps.withRenotes, withReplies: ps.withReplies, + withLocalOnly: ps.withLocalOnly, }, me), }); @@ -150,6 +153,7 @@ export default class extends Endpoint { // eslint- withFiles: boolean, withRenotes: boolean, withReplies: boolean, + withLocalOnly: boolean, }, me: MiLocalUser | null) { //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) @@ -169,6 +173,10 @@ export default class extends Endpoint { // eslint- } })); + if (!ps.withLocalOnly) { + query.andWhere('note.localOnly = FALSE'); + } + if (!ps.withReplies) { query.andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/stream/channels/vmimi-relay-hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/vmimi-relay-hybrid-timeline.ts index 229a52d411d1..eb0663f7f7d7 100644 --- a/packages/backend/src/server/api/stream/channels/vmimi-relay-hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/vmimi-relay-hybrid-timeline.ts @@ -22,6 +22,7 @@ class VmimiRelayHybridTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private withLocalOnly: boolean; constructor( private metaService: MetaService, @@ -44,6 +45,7 @@ class VmimiRelayHybridTimelineChannel extends Channel { this.withRenotes = params.withRenotes ?? true; this.withReplies = params.withReplies ?? false; this.withFiles = params.withFiles ?? false; + this.withLocalOnly = params.withLocalOnly ?? true; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -62,7 +64,7 @@ class VmimiRelayHybridTimelineChannel extends Channel { if (!( (note.channelId == null && isMe) || (note.channelId == null && Object.hasOwn(this.following, note.userId)) || - (note.channelId == null && (this.vmimiRelayTimelineService.isRelayedInstance(note.user.host) && note.visibility === 'public')) || + (note.channelId == null && (this.vmimiRelayTimelineService.isRelayedInstance(note.user.host) && note.visibility === 'public') && (this.withLocalOnly || !note.localOnly)) || (note.channelId != null && this.followingChannels.has(note.channelId)) )) return; diff --git a/packages/backend/src/server/api/stream/channels/vmimi-relay-timeline.ts b/packages/backend/src/server/api/stream/channels/vmimi-relay-timeline.ts index 3fd7068c1cc6..b07261419bea 100644 --- a/packages/backend/src/server/api/stream/channels/vmimi-relay-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/vmimi-relay-timeline.ts @@ -20,6 +20,7 @@ class VmimiRelayTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private withLocalOnly: boolean; constructor( private metaService: MetaService, @@ -41,6 +42,7 @@ class VmimiRelayTimelineChannel extends Channel { this.withRenotes = params.withRenotes ?? true; this.withReplies = params.withReplies ?? false; this.withFiles = params.withFiles ?? false; + this.withLocalOnly = params.withLocalOnly ?? true; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -51,6 +53,7 @@ class VmimiRelayTimelineChannel extends Channel { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; if (!this.vmimiRelayTimelineService.isRelayedInstance(note.user.host ?? null)) return; + if (!this.withLocalOnly && note.localOnly) return; if (note.visibility !== 'public') return; if (note.channelId != null) return; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index f8e7c41f77bf..d0deb2dc2984 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -38,10 +38,12 @@ const props = withDefaults(defineProps<{ withRenotes?: boolean; withReplies?: boolean; onlyFiles?: boolean; + withLocalOnly?: boolean; }>(), { withRenotes: true, withReplies: false, onlyFiles: false, + withLocalOnly: true, }); const emit = defineEmits<{ @@ -57,6 +59,7 @@ type TimelineQueryType = { withRenotes?: boolean, withReplies?: boolean, withFiles?: boolean, + withLocalOnly?: boolean, visibility?: string, listId?: string, channelId?: string, @@ -126,12 +129,14 @@ function connectChannel() { withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, withReplies: props.withReplies, + withLocalOnly: props.withLocalOnly, }); } else if (props.src === 'vmimi-relay-social') { connection = stream.useChannel('vmimiRelayHybridTimeline', { withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, withReplies: props.withReplies, + withLocalOnly: props.withLocalOnly, }); } else if (props.src === 'mentions') { connection = stream.useChannel('main'); @@ -211,6 +216,7 @@ function updatePaginationQuery() { withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, withReplies: props.withReplies, + withLocalOnly: props.withLocalOnly, }; } else if (props.src === 'vmimi-relay-social') { endpoint = 'notes/vmimi-relay-hybrid-timeline'; @@ -218,6 +224,7 @@ function updatePaginationQuery() { withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, withReplies: props.withReplies, + withLocalOnly: props.withLocalOnly, }; } else if (props.src === 'mentions') { endpoint = 'notes/mentions'; diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 49175b2b5a8a..bba107c23327 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -17,12 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only

@@ -76,6 +77,10 @@ const withRenotes = computed({ get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, set: (x) => saveTlFilter('withRenotes', x), }); +const withLocalOnly = computed({ + get: () => defaultStore.reactiveState.tl.value.filter.withLocalOnly, + set: (x) => saveTlFilter('withLocalOnly', x), +}); // computed内での無限ループを防ぐためのフラグ const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>('withReplies'); @@ -263,7 +268,11 @@ const headerActions = computed(() => { text: i18n.ts.fileAttachedOnly, ref: onlyFiles, disabled: src.value === 'local' || src.value === 'social' || src.value === 'vmimi-relay' || src.value === 'vmimi-relay-social' ? withReplies : false, - }], ev.currentTarget ?? ev.target); + }, src.value === 'vmimi-relay' || src.value === 'vmimi-relay-social' ? { + type: 'switch', + text: i18n.ts.showLocalOnlyInTimeline, + ref: withLocalOnly, + } : undefined], ev.currentTarget ?? ev.target); }, }, ]; diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 2b401a2b94d0..cae8aa584c0d 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -192,6 +192,7 @@ export const defaultStore = markRaw(new Storage('base', { withRenotes: true, withSensitive: true, onlyFiles: false, + withLocalOnly: true, }, }, }, diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts index 77b4d68e0839..c9dae2e6f3fb 100644 --- a/packages/frontend/src/ui/deck/deck-store.ts +++ b/packages/frontend/src/ui/deck/deck-store.ts @@ -34,6 +34,7 @@ export type Column = { withRenotes?: boolean; withReplies?: boolean; onlyFiles?: boolean; + withLocalOnly?: boolean; soundSetting: SoundStore; }; diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index 751adc076d0a..17b8f7d88855 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -25,11 +25,12 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -63,6 +64,7 @@ const soundSetting = ref(props.column.soundSetting ?? { type: null, const withRenotes = ref(props.column.withRenotes ?? true); const withReplies = ref(props.column.withReplies ?? false); const onlyFiles = ref(props.column.onlyFiles ?? false); +const withLocalOnly = ref(props.column.withLocalOnly ?? true); watch(withRenotes, v => { updateColumn(props.column.id, { @@ -82,6 +84,12 @@ watch(onlyFiles, v => { }); }); +watch(withLocalOnly, v => { + updateColumn(props.column.id, { + withLocalOnly: v, + }); +}); + watch(soundSetting, v => { updateColumn(props.column.id, { soundSetting: v }); }); @@ -150,7 +158,11 @@ const menu: MenuItem[] = [{ text: i18n.ts.fileAttachedOnly, ref: onlyFiles, disabled: props.column.tl === 'local' || props.column.tl === 'social' || props.column.tl === 'vmimi-relay-social' || props.column.tl === 'vmimi-relay' ? withReplies : false, -}]; +}, props.column.tl === 'vmimi-relay-social' || props.column.tl === 'vmimi-relay' ? { + type: 'switch', + text: i18n.ts.showLocalOnlyInTimeline, + ref: withLocalOnly, +} : undefined];