Skip to content

Commit 952fec5

Browse files
authored
feat: 過去のノートを非公開化/フォロワーのみ表示可能にできる機能 (#14814)
* wip * Update CHANGELOG.md * wip * wip * wip * Update privacy.vue * wip
1 parent 70b2a8f commit 952fec5

File tree

17 files changed

+281
-42
lines changed

17 files changed

+281
-42
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### General
44
- Feat: コンテンツの表示にログインを必須にできるように
5+
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
56

67
### Client
78
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように

locales/index.d.ts

+41-1
Original file line numberDiff line numberDiff line change
@@ -3806,6 +3806,18 @@ export interface Locale extends ILocale {
38063806
* 1ヶ月
38073807
*/
38083808
"oneMonth": string;
3809+
/**
3810+
* 3ヶ月
3811+
*/
3812+
"threeMonths": string;
3813+
/**
3814+
* 1年
3815+
*/
3816+
"oneYear": string;
3817+
/**
3818+
* 3日
3819+
*/
3820+
"threeDays": string;
38093821
/**
38103822
* 反映されるまで時間がかかる場合があります。
38113823
*/
@@ -5204,7 +5216,7 @@ export interface Locale extends ILocale {
52045216
*/
52055217
"requireSigninToViewContents": string;
52065218
/**
5207-
* あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーから情報を収集されるのを防ぐ効果が期待できます
5219+
* あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーに情報が収集されるのを防ぐ効果が期待できます
52085220
*/
52095221
"requireSigninToViewContentsDescription1": string;
52105222
/**
@@ -5215,6 +5227,34 @@ export interface Locale extends ILocale {
52155227
* リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。
52165228
*/
52175229
"requireSigninToViewContentsDescription3": string;
5230+
/**
5231+
* 過去のノートをフォロワーのみ表示可能にする
5232+
*/
5233+
"makeNotesFollowersOnlyBefore": string;
5234+
/**
5235+
* この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートがフォロワーのみ表示可能になります。無効に戻すと、ノートの公開状態も元に戻ります。
5236+
*/
5237+
"makeNotesFollowersOnlyBeforeDescription": string;
5238+
/**
5239+
* 過去のノートを非公開化する
5240+
*/
5241+
"makeNotesHiddenBefore": string;
5242+
/**
5243+
* この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。
5244+
*/
5245+
"makeNotesHiddenBeforeDescription": string;
5246+
/**
5247+
* リモートサーバーに連合されたノートには効果が及ばない場合があります。
5248+
*/
5249+
"mayNotEffectForFederatedNotes": string;
5250+
/**
5251+
* 指定した時間を経過しているノート
5252+
*/
5253+
"notesHavePassedSpecifiedPeriod": string;
5254+
/**
5255+
* 指定した日時より前のノート
5256+
*/
5257+
"notesOlderThanSpecifiedDateAndTime": string;
52185258
};
52195259
"_abuseUserReport": {
52205260
/**

locales/ja-JP.yml

+11-1
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,9 @@ oneHour: "1時間"
947947
oneDay: "1日"
948948
oneWeek: "1週間"
949949
oneMonth: "1ヶ月"
950+
threeMonths: "3ヶ月"
951+
oneYear: "1年"
952+
threeDays: "3日"
950953
reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
951954
failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
952955
rateLimitExceeded: "レート制限を超えました"
@@ -1298,9 +1301,16 @@ lockdown: "ロックダウン"
12981301

12991302
_accountSettings:
13001303
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
1301-
requireSigninToViewContentsDescription1: "あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーから情報を収集されるのを防ぐ効果が期待できます"
1304+
requireSigninToViewContentsDescription1: "あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーに情報が収集されるのを防ぐ効果が期待できます"
13021305
requireSigninToViewContentsDescription2: "URLプレビュー(OGP)、Webページへの埋め込み、ノートの引用に対応していないサーバーからの表示も不可になります。"
13031306
requireSigninToViewContentsDescription3: "リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。"
1307+
makeNotesFollowersOnlyBefore: "過去のノートをフォロワーのみ表示可能にする"
1308+
makeNotesFollowersOnlyBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートがフォロワーのみ表示可能になります。無効に戻すと、ノートの公開状態も元に戻ります。"
1309+
makeNotesHiddenBefore: "過去のノートを非公開化する"
1310+
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
1311+
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
1312+
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
1313+
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
13041314

13051315
_abuseUserReport:
13061316
forward: "転送"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* SPDX-FileCopyrightText: syuilo and misskey-project
3+
* SPDX-License-Identifier: AGPL-3.0-only
4+
*/
5+
6+
export class MakeNotesHiddenBefore1729486255072 {
7+
name = 'MakeNotesHiddenBefore1729486255072'
8+
9+
async up(queryRunner) {
10+
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesFollowersOnlyBefore" integer`);
11+
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesHiddenBefore" integer`);
12+
}
13+
14+
async down(queryRunner) {
15+
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesHiddenBefore"`);
16+
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesFollowersOnlyBefore"`);
17+
}
18+
}

packages/backend/src/core/WebhookTestService.ts

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
8484
isHibernated: false,
8585
isDeleted: false,
8686
requireSigninToViewContents: false,
87+
makeNotesFollowersOnlyBefore: null,
88+
makeNotesHiddenBefore: null,
8789
emojis: [],
8890
score: 0,
8991
host: null,

packages/backend/src/core/activitypub/ApRendererService.ts

+2
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ export class ApRendererService {
496496
_misskey_summary: profile.description,
497497
_misskey_followedMessage: profile.followedMessage,
498498
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
499+
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
500+
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
499501
icon: avatar ? this.renderImage(avatar) : null,
500502
image: banner ? this.renderImage(banner) : null,
501503
tag,

packages/backend/src/core/activitypub/misc/contexts.ts

+2
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,8 @@ const extension_context_definition = {
556556
'_misskey_summary': 'misskey:_misskey_summary',
557557
'_misskey_followedMessage': 'misskey:_misskey_followedMessage',
558558
'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
559+
'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
560+
'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
559561
'isCat': 'misskey:isCat',
560562
// vcard
561563
vcard: 'http://www.w3.org/2006/vcard/ns#',

packages/backend/src/core/activitypub/models/ApPersonService.ts

+2
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ export class ApPersonService implements OnModuleInit {
357357
isBot,
358358
isCat: (person as any).isCat === true,
359359
requireSigninToViewContents: (person as any).requireSigninToViewContents === true,
360+
makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null,
361+
makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null,
360362
emojis,
361363
})) as MiRemoteUser;
362364

packages/backend/src/core/activitypub/type.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface IObject {
1515
_misskey_summary?: string;
1616
_misskey_followedMessage?: string | null;
1717
_misskey_requireSigninToViewContents?: boolean;
18+
_misskey_makeNotesFollowersOnlyBefore?: number | null;
19+
_misskey_makeNotesHiddenBefore?: number | null;
1820
published?: string;
1921
cc?: ApObject;
2022
to?: ApObject;

packages/backend/src/core/entities/NoteEntityService.ts

+63-36
Original file line numberDiff line numberDiff line change
@@ -102,57 +102,83 @@ export class NoteEntityService implements OnModuleInit {
102102
}
103103

104104
@bindThis
105-
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) {
105+
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
106+
// FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある)
107+
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
108+
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
109+
if ((followersOnlyBefore != null)
110+
&& (
111+
(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
112+
|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
113+
)
114+
) {
115+
packedNote.visibility = 'followers';
116+
}
117+
}
118+
119+
if (meId === packedNote.userId) return;
120+
106121
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
107122
let hide = false;
108123

109-
// visibility が specified かつ自分が指定されていなかったら非表示
110-
if (packedNote.visibility === 'specified') {
111-
if (meId == null) {
124+
if (packedNote.user.requireSigninToViewContents && meId == null) {
125+
hide = true;
126+
}
127+
128+
if (!hide) {
129+
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
130+
if ((hiddenBefore != null)
131+
&& (
132+
(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
133+
|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
134+
)
135+
) {
112136
hide = true;
113-
} else if (meId === packedNote.userId) {
114-
hide = false;
115-
} else {
116-
// 指定されているかどうか
117-
const specified = packedNote.visibleUserIds!.some(id => meId === id);
137+
}
138+
}
118139

119-
if (specified) {
120-
hide = false;
121-
} else {
140+
// visibility が specified かつ自分が指定されていなかったら非表示
141+
if (!hide) {
142+
if (packedNote.visibility === 'specified') {
143+
if (meId == null) {
122144
hide = true;
145+
} else {
146+
// 指定されているかどうか
147+
const specified = packedNote.visibleUserIds!.some(id => meId === id);
148+
149+
if (!specified) {
150+
hide = true;
151+
}
123152
}
124153
}
125154
}
126155

127156
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
128-
if (packedNote.visibility === 'followers') {
129-
if (meId == null) {
130-
hide = true;
131-
} else if (meId === packedNote.userId) {
132-
hide = false;
133-
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
134-
// 自分の投稿に対するリプライ
135-
hide = false;
136-
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
137-
// 自分へのメンション
138-
hide = false;
139-
} else {
140-
// フォロワーかどうか
141-
const isFollowing = await this.followingsRepository.exists({
142-
where: {
143-
followeeId: packedNote.userId,
144-
followerId: meId,
145-
},
146-
});
157+
if (!hide) {
158+
if (packedNote.visibility === 'followers') {
159+
if (meId == null) {
160+
hide = true;
161+
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
162+
// 自分の投稿に対するリプライ
163+
hide = false;
164+
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
165+
// 自分へのメンション
166+
hide = false;
167+
} else {
168+
// フォロワーかどうか
169+
// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
170+
const isFollowing = await this.followingsRepository.exists({
171+
where: {
172+
followeeId: packedNote.userId,
173+
followerId: meId,
174+
},
175+
});
147176

148-
hide = !isFollowing;
177+
hide = !isFollowing;
178+
}
149179
}
150180
}
151181

152-
if (packedNote.user.requireSigninToViewContents && meId == null) {
153-
hide = true;
154-
}
155-
156182
if (hide) {
157183
packedNote.visibleUserIds = undefined;
158184
packedNote.fileIds = [];
@@ -161,6 +187,7 @@ export class NoteEntityService implements OnModuleInit {
161187
packedNote.poll = undefined;
162188
packedNote.cw = null;
163189
packedNote.isHidden = true;
190+
// TODO: hiddenReason みたいなのを提供しても良さそう
164191
}
165192
}
166193

packages/backend/src/core/entities/UserEntityService.ts

+2
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,8 @@ export class UserEntityService implements OnModuleInit {
491491
isBot: user.isBot,
492492
isCat: user.isCat,
493493
requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true,
494+
makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore ?? undefined,
495+
makeNotesHiddenBefore: user.makeNotesHiddenBefore ?? undefined,
494496
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
495497
name: instance.name,
496498
softwareName: instance.softwareName,

packages/backend/src/models/User.ts

+12
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,18 @@ export class MiUser {
207207
})
208208
public requireSigninToViewContents: boolean;
209209

210+
// in sec, マイナスで相対時間
211+
@Column('integer', {
212+
nullable: true,
213+
})
214+
public makeNotesFollowersOnlyBefore: number | null;
215+
216+
// in sec, マイナスで相対時間
217+
@Column('integer', {
218+
nullable: true,
219+
})
220+
public makeNotesHiddenBefore: number | null;
221+
210222
// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
211223
@Column('boolean', {
212224
default: false,

packages/backend/src/models/json-schema/user.ts

+8
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ export const packedUserLiteSchema = {
119119
type: 'boolean',
120120
nullable: false, optional: true,
121121
},
122+
makeNotesFollowersOnlyBefore: {
123+
type: 'number',
124+
nullable: true, optional: true,
125+
},
126+
makeNotesHiddenBefore: {
127+
type: 'number',
128+
nullable: true, optional: true,
129+
},
122130
instance: {
123131
type: 'object',
124132
nullable: false, optional: true,

packages/backend/src/server/api/endpoints/i/update.ts

+4
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ export const paramDef = {
180180
noCrawle: { type: 'boolean' },
181181
preventAiLearning: { type: 'boolean' },
182182
requireSigninToViewContents: { type: 'boolean' },
183+
makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true },
184+
makeNotesHiddenBefore: { type: 'integer', nullable: true },
183185
isBot: { type: 'boolean' },
184186
isCat: { type: 'boolean' },
185187
injectFeaturedNote: { type: 'boolean' },
@@ -336,6 +338,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
336338
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
337339
if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
338340
if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents;
341+
if ((typeof ps.makeNotesFollowersOnlyBefore === 'number') || (ps.makeNotesFollowersOnlyBefore === null)) updates.makeNotesFollowersOnlyBefore = ps.makeNotesFollowersOnlyBefore;
342+
if ((typeof ps.makeNotesHiddenBefore === 'number') || (ps.makeNotesHiddenBefore === null)) updates.makeNotesHiddenBefore = ps.makeNotesHiddenBefore;
339343
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
340344
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
341345
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;

packages/frontend/src/components/MkSelect.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import type { MenuItem } from '@/types/menu.js';
4646
import * as os from '@/os.js';
4747

4848
const props = defineProps<{
49-
modelValue: string | null;
49+
modelValue: string | number | null;
5050
required?: boolean;
5151
readonly?: boolean;
5252
disabled?: boolean;

0 commit comments

Comments
 (0)