This commit is contained in:
syuilo 2024-10-21 16:54:04 +09:00
parent 9d0f7eeb9c
commit 7a37ccc96d
16 changed files with 176 additions and 44 deletions

12
locales/index.d.ts vendored
View File

@ -5215,6 +5215,18 @@ export interface Locale extends ILocale {
* *
*/ */
"requireSigninToViewContentsDescription3": string; "requireSigninToViewContentsDescription3": string;
/**
*
*/
"makeNotesFollowersOnlyBefore": string;
/**
*
*/
"makeNotesHiddenBefore": string;
/**
*
*/
"mayNotEffectForFederatedNotes": string;
}; };
"_abuseUserReport": { "_abuseUserReport": {
/** /**

View File

@ -1301,6 +1301,9 @@ _accountSettings:
requireSigninToViewContentsDescription1: "あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーから情報を収集されるのを防ぐ効果が期待できます。" requireSigninToViewContentsDescription1: "あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーから情報を収集されるのを防ぐ効果が期待できます。"
requireSigninToViewContentsDescription2: "URLプレビュー(OGP)、Webページへの埋め込み、ートの引用に対応していないサーバーからの表示も不可になります。" requireSigninToViewContentsDescription2: "URLプレビュー(OGP)、Webページへの埋め込み、ートの引用に対応していないサーバーからの表示も不可になります。"
requireSigninToViewContentsDescription3: "リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。" requireSigninToViewContentsDescription3: "リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。"
makeNotesFollowersOnlyBefore: "過去のノートをフォロワーのみ表示可能にする"
makeNotesHiddenBefore: "過去のノートを非公開化する"
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
_abuseUserReport: _abuseUserReport:
forward: "転送" forward: "転送"

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class MakeNotesHiddenBefore1729486255072 {
name = 'MakeNotesHiddenBefore1729486255072'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesFollowersOnlyBefore" integer`);
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesHiddenBefore" integer`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesHiddenBefore"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesFollowersOnlyBefore"`);
}
}

View File

@ -84,6 +84,8 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
isHibernated: false, isHibernated: false,
isDeleted: false, isDeleted: false,
requireSigninToViewContents: false, requireSigninToViewContents: false,
makeNotesFollowersOnlyBefore: null,
makeNotesHiddenBefore: null,
emojis: [], emojis: [],
score: 0, score: 0,
host: null, host: null,

View File

@ -496,6 +496,8 @@ export class ApRendererService {
_misskey_summary: profile.description, _misskey_summary: profile.description,
_misskey_followedMessage: profile.followedMessage, _misskey_followedMessage: profile.followedMessage,
_misskey_requireSigninToViewContents: user.requireSigninToViewContents, _misskey_requireSigninToViewContents: user.requireSigninToViewContents,
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
icon: avatar ? this.renderImage(avatar) : null, icon: avatar ? this.renderImage(avatar) : null,
image: banner ? this.renderImage(banner) : null, image: banner ? this.renderImage(banner) : null,
tag, tag,

View File

@ -556,6 +556,8 @@ const extension_context_definition = {
'_misskey_summary': 'misskey:_misskey_summary', '_misskey_summary': 'misskey:_misskey_summary',
'_misskey_followedMessage': 'misskey:_misskey_followedMessage', '_misskey_followedMessage': 'misskey:_misskey_followedMessage',
'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents', '_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
'isCat': 'misskey:isCat', 'isCat': 'misskey:isCat',
// vcard // vcard
vcard: 'http://www.w3.org/2006/vcard/ns#', vcard: 'http://www.w3.org/2006/vcard/ns#',

View File

@ -357,6 +357,8 @@ export class ApPersonService implements OnModuleInit {
isBot, isBot,
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
requireSigninToViewContents: (person as any).requireSigninToViewContents === true, requireSigninToViewContents: (person as any).requireSigninToViewContents === true,
makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null,
makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null,
emojis, emojis,
})) as MiRemoteUser; })) as MiRemoteUser;

View File

@ -15,6 +15,8 @@ export interface IObject {
_misskey_summary?: string; _misskey_summary?: string;
_misskey_followedMessage?: string | null; _misskey_followedMessage?: string | null;
_misskey_requireSigninToViewContents?: boolean; _misskey_requireSigninToViewContents?: boolean;
_misskey_makeNotesFollowersOnlyBefore?: number | null;
_misskey_makeNotesHiddenBefore?: number | null;
published?: string; published?: string;
cc?: ApObject; cc?: ApObject;
to?: ApObject; to?: ApObject;

View File

@ -102,57 +102,83 @@ export class NoteEntityService implements OnModuleInit {
} }
@bindThis @bindThis
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) { private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
// FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある)
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
if ((followersOnlyBefore != null)
&& (
(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
)
) {
packedNote.visibility = 'followers';
}
}
if (meId === packedNote.userId) return;
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false; let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示 if (packedNote.user.requireSigninToViewContents && meId == null) {
if (packedNote.visibility === 'specified') { hide = true;
if (meId == null) { }
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some(id => meId === id);
if (specified) { if (!hide) {
hide = false; const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
} else { if ((hiddenBefore != null)
&& (
(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
)
) {
hide = true;
}
}
// visibility が specified かつ自分が指定されていなかったら非表示
if (!hide) {
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true; hide = true;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some(id => meId === id);
if (!specified) {
hide = true;
}
} }
} }
} }
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') { if (!hide) {
if (meId == null) { if (packedNote.visibility === 'followers') {
hide = true; if (meId == null) {
} else if (meId === packedNote.userId) { hide = true;
hide = false; } else if (packedNote.reply && (meId === packedNote.reply.userId)) {
} else if (packedNote.reply && (meId === packedNote.reply.userId)) { // 自分の投稿に対するリプライ
// 自分の投稿に対するリプライ hide = false;
hide = false; } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { // 自分へのメンション
// 自分へのメンション hide = false;
hide = false; } else {
} else { // フォロワーかどうか
// フォロワーかどうか // TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
const isFollowing = await this.followingsRepository.exists({ const isFollowing = await this.followingsRepository.exists({
where: { where: {
followeeId: packedNote.userId, followeeId: packedNote.userId,
followerId: meId, followerId: meId,
}, },
}); });
hide = !isFollowing; hide = !isFollowing;
}
} }
} }
if (packedNote.user.requireSigninToViewContents && meId == null) {
hide = true;
}
if (hide) { if (hide) {
packedNote.visibleUserIds = undefined; packedNote.visibleUserIds = undefined;
packedNote.fileIds = []; packedNote.fileIds = [];
@ -161,6 +187,7 @@ export class NoteEntityService implements OnModuleInit {
packedNote.poll = undefined; packedNote.poll = undefined;
packedNote.cw = null; packedNote.cw = null;
packedNote.isHidden = true; packedNote.isHidden = true;
// TODO: hiddenReason みたいなのを提供しても良さそう
} }
} }

View File

@ -491,6 +491,8 @@ export class UserEntityService implements OnModuleInit {
isBot: user.isBot, isBot: user.isBot,
isCat: user.isCat, isCat: user.isCat,
requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true, requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true,
makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore ?? undefined,
makeNotesHiddenBefore: user.makeNotesHiddenBefore ?? undefined,
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? { instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
name: instance.name, name: instance.name,
softwareName: instance.softwareName, softwareName: instance.softwareName,

View File

@ -207,6 +207,18 @@ export class MiUser {
}) })
public requireSigninToViewContents: boolean; public requireSigninToViewContents: boolean;
// in sec, マイナスで相対時間
@Column('integer', {
nullable: true,
})
public makeNotesFollowersOnlyBefore: number | null;
// in sec, マイナスで相対時間
@Column('integer', {
nullable: true,
})
public makeNotesHiddenBefore: number | null;
// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ // アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
@Column('boolean', { @Column('boolean', {
default: false, default: false,

View File

@ -119,6 +119,14 @@ export const packedUserLiteSchema = {
type: 'boolean', type: 'boolean',
nullable: false, optional: true, nullable: false, optional: true,
}, },
makeNotesFollowersOnlyBefore: {
type: 'number',
nullable: true, optional: true,
},
makeNotesHiddenBefore: {
type: 'number',
nullable: true, optional: true,
},
instance: { instance: {
type: 'object', type: 'object',
nullable: false, optional: true, nullable: false, optional: true,

View File

@ -180,6 +180,8 @@ export const paramDef = {
noCrawle: { type: 'boolean' }, noCrawle: { type: 'boolean' },
preventAiLearning: { type: 'boolean' }, preventAiLearning: { type: 'boolean' },
requireSigninToViewContents: { type: 'boolean' }, requireSigninToViewContents: { type: 'boolean' },
makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true },
makeNotesHiddenBefore: { type: 'integer', nullable: true },
isBot: { type: 'boolean' }, isBot: { type: 'boolean' },
isCat: { type: 'boolean' }, isCat: { type: 'boolean' },
injectFeaturedNote: { type: 'boolean' }, injectFeaturedNote: { type: 'boolean' },
@ -336,6 +338,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning; if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents; if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents;
if ((typeof ps.makeNotesFollowersOnlyBefore === 'number') || (ps.makeNotesFollowersOnlyBefore === null)) updates.makeNotesFollowersOnlyBefore = ps.makeNotesFollowersOnlyBefore;
if ((typeof ps.makeNotesHiddenBefore === 'number') || (ps.makeNotesHiddenBefore === null)) updates.makeNotesHiddenBefore = ps.makeNotesHiddenBefore;
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;

View File

@ -42,14 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue'; import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
import { useInterval } from '@@/js/use-interval.js';
import type { MenuItem } from '@/types/menu.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
modelValue: string | null; modelValue: string | number | null;
required?: boolean; required?: boolean;
readonly?: boolean; readonly?: boolean;
disabled?: boolean; disabled?: boolean;
@ -62,8 +62,8 @@ const props = defineProps<{
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'changeByUser', value: string | null): void; (ev: 'changeByUser', value: string | number | null): void;
(ev: 'update:modelValue', value: string | null): void; (ev: 'update:modelValue', value: string | number | null): void;
}>(); }>();
const slots = useSlots(); const slots = useSlots();

View File

@ -45,17 +45,45 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
<FormSection> <FormSection>
<template #label>{{ i18n.ts.lockdown }}</template> <template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkSwitch v-model="requireSigninToViewContents" @update:modelValue="save()"> <MkSwitch v-model="requireSigninToViewContents" @update:modelValue="save()">
{{ i18n.ts._accountSettings.requireSigninToViewContents }}<span class="_beta">{{ i18n.ts.beta }}</span> {{ i18n.ts._accountSettings.requireSigninToViewContents }}
<template #caption> <template #caption>
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div> <div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div> <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div> <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
</template> </template>
</MkSwitch> </MkSwitch>
<MkSelect v-model="makeNotesFollowersOnlyBefore" @update:modelValue="save()">
<template #label>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</template>
<option :value="null">{{ i18n.ts.none }}</option>
<option :value="-3600">{{ '1h ago' }}</option>
<option :value="-86400">{{ '1d ago' }}</option>
<option :value="-259200">{{ '3d ago' }}</option>
<option :value="-604800">{{ '1w ago' }}</option>
<option :value="-2592000">{{ '1m ago' }}</option>
<option :value="-31104000">{{ '1y ago' }}</option>
<template #caption>
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
</template>
</MkSelect>
<MkSelect v-model="makeNotesHiddenBefore" @update:modelValue="save()">
<template #label>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</template>
<option :value="null">{{ i18n.ts.none }}</option>
<option :value="-3600">{{ '1h ago' }}</option>
<option :value="-86400">{{ '1d ago' }}</option>
<option :value="-259200">{{ '3d ago' }}</option>
<option :value="-604800">{{ '1w ago' }}</option>
<option :value="-2592000">{{ '1m ago' }}</option>
<option :value="-31104000">{{ '1y ago' }}</option>
<template #caption>
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
</template>
</MkSelect>
</div> </div>
</FormSection> </FormSection>
@ -106,6 +134,8 @@ const noCrawle = ref($i.noCrawle);
const preventAiLearning = ref($i.preventAiLearning); const preventAiLearning = ref($i.preventAiLearning);
const isExplorable = ref($i.isExplorable); const isExplorable = ref($i.isExplorable);
const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false); const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false);
const makeNotesFollowersOnlyBefore = ref($i.makeNotesFollowersOnlyBefore ?? null);
const makeNotesHiddenBefore = ref($i.makeNotesHiddenBefore ?? null);
const hideOnlineStatus = ref($i.hideOnlineStatus); const hideOnlineStatus = ref($i.hideOnlineStatus);
const publicReactions = ref($i.publicReactions); const publicReactions = ref($i.publicReactions);
const followingVisibility = ref($i.followingVisibility); const followingVisibility = ref($i.followingVisibility);
@ -124,6 +154,8 @@ function save() {
preventAiLearning: !!preventAiLearning.value, preventAiLearning: !!preventAiLearning.value,
isExplorable: !!isExplorable.value, isExplorable: !!isExplorable.value,
requireSigninToViewContents: !!requireSigninToViewContents.value, requireSigninToViewContents: !!requireSigninToViewContents.value,
makeNotesFollowersOnlyBefore: makeNotesFollowersOnlyBefore.value,
makeNotesHiddenBefore: makeNotesHiddenBefore.value,
hideOnlineStatus: !!hideOnlineStatus.value, hideOnlineStatus: !!hideOnlineStatus.value,
publicReactions: !!publicReactions.value, publicReactions: !!publicReactions.value,
followingVisibility: followingVisibility.value, followingVisibility: followingVisibility.value,

View File

@ -3737,6 +3737,8 @@ export type components = {
isBot?: boolean; isBot?: boolean;
isCat?: boolean; isCat?: boolean;
requireSigninToViewContents?: boolean; requireSigninToViewContents?: boolean;
makeNotesFollowersOnlyBefore?: number | null;
makeNotesHiddenBefore?: number | null;
instance?: { instance?: {
name: string | null; name: string | null;
softwareName: string | null; softwareName: string | null;
@ -19846,6 +19848,8 @@ export type operations = {
noCrawle?: boolean; noCrawle?: boolean;
preventAiLearning?: boolean; preventAiLearning?: boolean;
requireSigninToViewContents?: boolean; requireSigninToViewContents?: boolean;
makeNotesFollowersOnlyBefore?: number | null;
makeNotesHiddenBefore?: number | null;
isBot?: boolean; isBot?: boolean;
isCat?: boolean; isCat?: boolean;
injectFeaturedNote?: boolean; injectFeaturedNote?: boolean;