feat: AIによるNSFW検出を無視できるポリシーを追加 (MisskeyIO#500)
* feat: AIによるNSFW検出を無視できるポリシーを追加 * refactor: skipNsfwCheckの条件を同じようにまとめた
This commit is contained in:
parent
c0dbdd78c1
commit
d624547874
|
@ -1698,6 +1698,7 @@ _role:
|
||||||
canManageAvatarDecorations: "Manage avatar decorations"
|
canManageAvatarDecorations: "Manage avatar decorations"
|
||||||
driveCapacity: "Drive capacity"
|
driveCapacity: "Drive capacity"
|
||||||
alwaysMarkNsfw: "Always mark files as NSFW"
|
alwaysMarkNsfw: "Always mark files as NSFW"
|
||||||
|
skipNsfwDetection: "Skip NSFW detection by AI"
|
||||||
pinMax: "Maximum number of pinned notes"
|
pinMax: "Maximum number of pinned notes"
|
||||||
antennaMax: "Maximum number of antennas"
|
antennaMax: "Maximum number of antennas"
|
||||||
antennaNotesMax: "Maximum number of notes stored in antennas"
|
antennaNotesMax: "Maximum number of notes stored in antennas"
|
||||||
|
|
|
@ -6622,6 +6622,10 @@ export interface Locale extends ILocale {
|
||||||
* ファイルにNSFWを常に付与
|
* ファイルにNSFWを常に付与
|
||||||
*/
|
*/
|
||||||
"alwaysMarkNsfw": string;
|
"alwaysMarkNsfw": string;
|
||||||
|
/**
|
||||||
|
* AIによるNSFW検出を無視
|
||||||
|
*/
|
||||||
|
"skipNsfwDetection": string;
|
||||||
/**
|
/**
|
||||||
* ノートのピン留めの最大数
|
* ノートのピン留めの最大数
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1713,6 +1713,7 @@ _role:
|
||||||
canManageAvatarDecorations: "アバターデコレーションの管理"
|
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||||
driveCapacity: "ドライブ容量"
|
driveCapacity: "ドライブ容量"
|
||||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||||
|
skipNsfwDetection: "AIによるNSFW検出を無視"
|
||||||
pinMax: "ノートのピン留めの最大数"
|
pinMax: "ノートのピン留めの最大数"
|
||||||
antennaMax: "アンテナの作成可能数"
|
antennaMax: "アンテナの作成可能数"
|
||||||
antennaNotesMax: "アンテナに保持する最大ノート数"
|
antennaNotesMax: "アンテナに保持する最大ノート数"
|
||||||
|
|
|
@ -459,15 +459,14 @@ export class DriveService {
|
||||||
}: AddFileArgs): Promise<MiDriveFile> {
|
}: AddFileArgs): Promise<MiDriveFile> {
|
||||||
let skipNsfwCheck = false;
|
let skipNsfwCheck = false;
|
||||||
const instance = await this.metaService.fetch();
|
const instance = await this.metaService.fetch();
|
||||||
const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
|
const policies = user && await this.roleService.getUserPolicies(user.id);
|
||||||
if (user == null) {
|
const userRoleNSFW = policies?.alwaysMarkNsfw;
|
||||||
skipNsfwCheck = true;
|
skipNsfwCheck ||= user == null;
|
||||||
} else if (userRoleNSFW) {
|
skipNsfwCheck ||= !!userRoleNSFW;
|
||||||
skipNsfwCheck = true;
|
skipNsfwCheck ||= !!policies?.skipNsfwDetection;
|
||||||
}
|
skipNsfwCheck ||= instance.sensitiveMediaDetection === 'none';
|
||||||
if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
|
skipNsfwCheck ||= !!(user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user));
|
||||||
if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
|
skipNsfwCheck ||= !!(user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user));
|
||||||
if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
|
|
||||||
|
|
||||||
const info = await this.fileInfoService.getFileInfo(path, {
|
const info = await this.fileInfoService.getFileInfo(path, {
|
||||||
skipSensitiveDetection: skipNsfwCheck,
|
skipSensitiveDetection: skipNsfwCheck,
|
||||||
|
@ -511,11 +510,10 @@ export class DriveService {
|
||||||
this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`);
|
this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`);
|
||||||
|
|
||||||
//#region Check drive usage
|
//#region Check drive usage
|
||||||
if (user && !isLink) {
|
if (user && policies && !isLink) {
|
||||||
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
||||||
const isLocalUser = this.userEntityService.isLocalUser(user);
|
const isLocalUser = this.userEntityService.isLocalUser(user);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(user.id);
|
|
||||||
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
||||||
this.registerLogger.debug('drive capacity override applied');
|
this.registerLogger.debug('drive capacity override applied');
|
||||||
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||||
|
|
|
@ -55,6 +55,7 @@ export type RolePolicies = {
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
driveCapacityMb: number;
|
driveCapacityMb: number;
|
||||||
alwaysMarkNsfw: boolean;
|
alwaysMarkNsfw: boolean;
|
||||||
|
skipNsfwDetection: boolean;
|
||||||
pinLimit: number;
|
pinLimit: number;
|
||||||
antennaLimit: number;
|
antennaLimit: number;
|
||||||
antennaNotesLimit: number;
|
antennaNotesLimit: number;
|
||||||
|
@ -91,6 +92,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
driveCapacityMb: 100,
|
driveCapacityMb: 100,
|
||||||
alwaysMarkNsfw: false,
|
alwaysMarkNsfw: false,
|
||||||
|
skipNsfwDetection: false,
|
||||||
pinLimit: 5,
|
pinLimit: 5,
|
||||||
antennaLimit: 5,
|
antennaLimit: 5,
|
||||||
antennaNotesLimit: 200,
|
antennaNotesLimit: 200,
|
||||||
|
@ -366,6 +368,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||||
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
||||||
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
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)),
|
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
||||||
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
|
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
|
||||||
antennaNotesLimit: calc('antennaNotesLimit', vs => Math.max(...vs)),
|
antennaNotesLimit: calc('antennaNotesLimit', vs => Math.max(...vs)),
|
||||||
|
|
|
@ -239,6 +239,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
skipNsfwDetection: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
pinLimit: {
|
pinLimit: {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -94,6 +94,7 @@ export const ROLE_POLICIES = [
|
||||||
'canHideAds',
|
'canHideAds',
|
||||||
'driveCapacityMb',
|
'driveCapacityMb',
|
||||||
'alwaysMarkNsfw',
|
'alwaysMarkNsfw',
|
||||||
|
'skipNsfwDetection',
|
||||||
'pinLimit',
|
'pinLimit',
|
||||||
'antennaLimit',
|
'antennaLimit',
|
||||||
'antennaNotesLimit',
|
'antennaNotesLimit',
|
||||||
|
|
|
@ -518,6 +518,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.skipNsfwDetection, 'skipNsfwDetection'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.skipNsfwDetection }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.skipNsfwDetection.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.skipNsfwDetection.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.skipNsfwDetection)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.skipNsfwDetection.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.skipNsfwDetection.value" :disabled="role.policies.skipNsfwDetection.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.skipNsfwDetection.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.pinMax, 'pinLimit'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
|
||||||
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
|
|
@ -190,6 +190,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.skipNsfwDetection, 'skipNsfwDetection'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.skipNsfwDetection }}</template>
|
||||||
|
<template #suffix>{{ policies.skipNsfwDetection ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.skipNsfwDetection">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
|
||||||
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
||||||
<template #suffix>{{ policies.pinLimit }}</template>
|
<template #suffix>{{ policies.pinLimit }}</template>
|
||||||
|
|
|
@ -4822,6 +4822,7 @@ export type components = {
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
driveCapacityMb: number;
|
driveCapacityMb: number;
|
||||||
alwaysMarkNsfw: boolean;
|
alwaysMarkNsfw: boolean;
|
||||||
|
skipNsfwDetection: boolean;
|
||||||
pinLimit: number;
|
pinLimit: number;
|
||||||
antennaLimit: number;
|
antennaLimit: number;
|
||||||
antennaNotesLimit: number;
|
antennaNotesLimit: number;
|
||||||
|
|
Loading…
Reference in New Issue