From c0dbdd78c1c20c60b556d0cda9b24087edece72a Mon Sep 17 00:00:00 2001
From: kabo2468 <28654659+kabo2468@users.noreply.github.com>
Date: Sun, 3 Mar 2024 02:42:13 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20=E3=82=A2=E3=83=B3=E3=83=86=E3=83=8A?=
 =?UTF-8?q?=E3=81=AB=E4=BF=9D=E6=8C=81=E3=81=99=E3=82=8B=E3=83=8E=E3=83=BC?=
 =?UTF-8?q?=E3=83=88=E6=95=B0=E3=82=92=E3=83=9D=E3=83=AA=E3=82=B7=E3=83=BC?=
 =?UTF-8?q?=E3=81=AB=E8=BF=BD=E5=8A=A0=20(MisskeyIO#499)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/en-US.yml                             |  1 +
 locales/index.d.ts                            |  4 ++++
 locales/ja-JP.yml                             |  1 +
 packages/backend/src/core/AntennaService.ts   |  6 +++++-
 packages/backend/src/core/RoleService.ts      |  3 +++
 .../backend/src/models/json-schema/role.ts    |  4 ++++
 packages/frontend/src/const.ts                |  1 +
 .../frontend/src/pages/admin/roles.editor.vue | 19 +++++++++++++++++++
 packages/frontend/src/pages/admin/roles.vue   |  7 +++++++
 packages/misskey-js/src/autogen/types.ts      |  1 +
 10 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index b0d5ec01dd..7bfc5d3e44 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1700,6 +1700,7 @@ _role:
     alwaysMarkNsfw: "Always mark files as NSFW"
     pinMax: "Maximum number of pinned notes"
     antennaMax: "Maximum number of antennas"
+    antennaNotesMax: "Maximum number of notes stored in antennas"
     wordMuteMax: "Maximum number of characters allowed in word mutes"
     webhookMax: "Maximum number of Webhooks"
     clipMax: "Maximum number of Clips"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 412c1f1866..c027b07b32 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -6630,6 +6630,10 @@ export interface Locale extends ILocale {
              * アンテナの作成可能数
              */
             "antennaMax": string;
+            /**
+             * アンテナに保持する最大ノート数
+             */
+            "antennaNotesMax": string;
             /**
              * ワードミュートの最大文字数
              */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index bcb8d92fbf..0efd769737 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1715,6 +1715,7 @@ _role:
     alwaysMarkNsfw: "ファイルにNSFWを常に付与"
     pinMax: "ノートのピン留めの最大数"
     antennaMax: "アンテナの作成可能数"
+    antennaNotesMax: "アンテナに保持する最大ノート数"
     wordMuteMax: "ワードミュートの最大文字数"
     webhookMax: "Webhookの作成可能数"
     clipMax: "クリップの作成可能数"
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 7e7b1d8654..adddd29ebc 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -17,6 +17,7 @@ import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import type { GlobalEvents } from '@/core/GlobalEventService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import { RoleService } from '@/core/RoleService.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
@@ -40,6 +41,7 @@ export class AntennaService implements OnApplicationShutdown {
 		private utilityService: UtilityService,
 		private globalEventService: GlobalEventService,
 		private fanoutTimelineService: FanoutTimelineService,
+		private roleService: RoleService,
 	) {
 		this.antennasFetched = false;
 		this.antennas = [];
@@ -102,8 +104,10 @@ export class AntennaService implements OnApplicationShutdown {
 
 		const redisPipeline = this.redisForTimelines.pipeline();
 
+		const { antennaNotesLimit } = await this.roleService.getUserPolicies(noteUser.id);
+
 		for (const antenna of matchedAntennas) {
-			this.fanoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
+			this.fanoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, antennaNotesLimit, redisPipeline);
 			this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
 		}
 
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index fdf86b7d08..5db2e6fc05 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -57,6 +57,7 @@ export type RolePolicies = {
 	alwaysMarkNsfw: boolean;
 	pinLimit: number;
 	antennaLimit: number;
+	antennaNotesLimit: number;
 	wordMuteLimit: number;
 	webhookLimit: number;
 	clipLimit: number;
@@ -92,6 +93,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	alwaysMarkNsfw: false,
 	pinLimit: 5,
 	antennaLimit: 5,
+	antennaNotesLimit: 200,
 	wordMuteLimit: 200,
 	webhookLimit: 3,
 	clipLimit: 10,
@@ -366,6 +368,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 			alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
 			pinLimit: calc('pinLimit', vs => Math.max(...vs)),
 			antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
+			antennaNotesLimit: calc('antennaNotesLimit', vs => Math.max(...vs)),
 			wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
 			webhookLimit: calc('webhookLimit', vs => Math.max(...vs)),
 			clipLimit: calc('clipLimit', vs => Math.max(...vs)),
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index b0d1e0a2c5..c39e3824e9 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -247,6 +247,10 @@ export const packedRolePoliciesSchema = {
 			type: 'integer',
 			optional: false, nullable: false,
 		},
+		antennaNotesLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
 		wordMuteLimit: {
 			type: 'integer',
 			optional: false, nullable: false,
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index 8b1856c14f..5edd983065 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -96,6 +96,7 @@ export const ROLE_POLICIES = [
 	'alwaysMarkNsfw',
 	'pinLimit',
 	'antennaLimit',
+	'antennaNotesLimit',
 	'wordMuteLimit',
 	'webhookLimit',
 	'clipLimit',
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 2306269db2..43c595a33a 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -556,6 +556,25 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</MkFolder>
 
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.antennaNotesMax, 'antennaNotesLimit'])">
+				<template #label>{{ i18n.ts._role._options.antennaNotesMax }}</template>
+				<template #suffix>
+					<span v-if="role.policies.antennaNotesLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.antennaNotesLimit.value }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.antennaNotesLimit)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.antennaNotesLimit.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkInput v-model="role.policies.antennaNotesLimit.value" :disabled="role.policies.antennaNotesLimit.useDefault" type="number" :readonly="readonly">
+					</MkInput>
+					<MkRange v-model="role.policies.antennaNotesLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
 			<MkFolder v-if="matchQuery([i18n.ts._role._options.wordMuteMax, 'wordMuteLimit'])">
 				<template #label>{{ i18n.ts._role._options.wordMuteMax }}</template>
 				<template #suffix>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 5d71b20f45..c143548d68 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -204,6 +204,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</MkInput>
 						</MkFolder>
 
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.antennaNotesMax, 'antennaNotesLimit'])">
+							<template #label>{{ i18n.ts._role._options.antennaNotesMax }}</template>
+							<template #suffix>{{ policies.antennaNotesLimit }}</template>
+							<MkInput v-model="policies.antennaNotesLimit" type="number">
+							</MkInput>
+						</MkFolder>
+
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.wordMuteMax, 'wordMuteLimit'])">
 							<template #label>{{ i18n.ts._role._options.wordMuteMax }}</template>
 							<template #suffix>{{ policies.wordMuteLimit }}</template>
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 5cc1b1d257..be369b6ec2 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4824,6 +4824,7 @@ export type components = {
       alwaysMarkNsfw: boolean;
       pinLimit: number;
       antennaLimit: number;
+      antennaNotesLimit: number;
       wordMuteLimit: number;
       webhookLimit: number;
       clipLimit: number;