diff --git a/locales/en-US.yml b/locales/en-US.yml index 7bfc5d3e44..569484a092 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1698,6 +1698,7 @@ _role: canManageAvatarDecorations: "Manage avatar decorations" driveCapacity: "Drive capacity" alwaysMarkNsfw: "Always mark files as NSFW" + skipNsfwDetection: "Skip NSFW detection by AI" pinMax: "Maximum number of pinned notes" antennaMax: "Maximum number of antennas" antennaNotesMax: "Maximum number of notes stored in antennas" diff --git a/locales/index.d.ts b/locales/index.d.ts index c027b07b32..59bc5b5c16 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6622,6 +6622,10 @@ export interface Locale extends ILocale { * ファイルにNSFWを常に付与 */ "alwaysMarkNsfw": string; + /** + * AIによるNSFW検出を無視 + */ + "skipNsfwDetection": string; /** * ノートのピン留めの最大数 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0efd769737..8065500bdf 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1713,6 +1713,7 @@ _role: canManageAvatarDecorations: "アバターデコレーションの管理" driveCapacity: "ドライブ容量" alwaysMarkNsfw: "ファイルにNSFWを常に付与" + skipNsfwDetection: "AIによるNSFW検出を無視" pinMax: "ノートのピン留めの最大数" antennaMax: "アンテナの作成可能数" antennaNotesMax: "アンテナに保持する最大ノート数" diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index dd21f0ab00..b15fbeaab8 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -459,15 +459,14 @@ export class DriveService { }: AddFileArgs): Promise { let skipNsfwCheck = false; const instance = await this.metaService.fetch(); - const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw; - if (user == null) { - skipNsfwCheck = true; - } else if (userRoleNSFW) { - skipNsfwCheck = true; - } - if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true; + const policies = user && await this.roleService.getUserPolicies(user.id); + const userRoleNSFW = policies?.alwaysMarkNsfw; + skipNsfwCheck ||= user == null; + skipNsfwCheck ||= !!userRoleNSFW; + skipNsfwCheck ||= !!policies?.skipNsfwDetection; + skipNsfwCheck ||= instance.sensitiveMediaDetection === 'none'; + skipNsfwCheck ||= !!(user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)); + skipNsfwCheck ||= !!(user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)); const info = await this.fileInfoService.getFileInfo(path, { skipSensitiveDetection: skipNsfwCheck, @@ -511,11 +510,10 @@ export class DriveService { this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`); //#region Check drive usage - if (user && !isLink) { + if (user && policies && !isLink) { const usage = await this.driveFileEntityService.calcDriveUsageOf(user); const isLocalUser = this.userEntityService.isLocalUser(user); - const policies = await this.roleService.getUserPolicies(user.id); const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; this.registerLogger.debug('drive capacity override applied'); this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 5db2e6fc05..b0217679ed 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -55,6 +55,7 @@ export type RolePolicies = { canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; + skipNsfwDetection: boolean; pinLimit: number; antennaLimit: number; antennaNotesLimit: number; @@ -91,6 +92,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canHideAds: false, driveCapacityMb: 100, alwaysMarkNsfw: false, + skipNsfwDetection: false, pinLimit: 5, antennaLimit: 5, antennaNotesLimit: 200, @@ -366,6 +368,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), + skipNsfwDetection: calc('skipNsfwDetection', 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)), diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index c39e3824e9..e757d109e8 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -239,6 +239,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + skipNsfwDetection: { + type: 'boolean', + optional: false, nullable: false, + }, pinLimit: { type: 'integer', optional: false, nullable: false, diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 5edd983065..9ce2ceddf0 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -94,6 +94,7 @@ export const ROLE_POLICIES = [ 'canHideAds', 'driveCapacityMb', 'alwaysMarkNsfw', + 'skipNsfwDetection', 'pinLimit', 'antennaLimit', 'antennaNotesLimit', diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 43c595a33a..ee6ea14464 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -518,6 +518,26 @@ SPDX-License-Identifier: AGPL-3.0-only + + + +
+ + + + + + + + + +
+
+