mirror of
https://example.com
synced 2024-11-22 10:26:39 +09:00
refactor: remove NSFW media detection
This commit is contained in:
parent
65609b27c2
commit
261b8e6218
32 changed files with 137 additions and 11788 deletions
|
@ -108,6 +108,8 @@
|
|||
|
||||
### 主要な変更点
|
||||
|
||||
- センシティブなメディアの自動検出機能を削除
|
||||
- 誤検知が多く、使っているサーバーもあまり多くないと思ったため(困る場合には教えてください)
|
||||
- 非ログインユーザーにもローカルタイムラインとグローバルタイムラインを公開できるように変更
|
||||
- コントロールパネルから設定すると `https://server.example.com/timeline` で公開されます
|
||||
- 全文検索のエンジンを [PGroonga](https://pgroonga.github.io/) に変更([tamaina さんの PGroonga パッチ](https://gist.github.com/tamaina/29e88e02d0dc5acfa2f50b3347d3d20d)を拡張)
|
||||
|
|
|
@ -270,8 +270,6 @@ const {
|
|||
readEnvironmentConfig,
|
||||
readServerConfig,
|
||||
AntennaSrcEnum,
|
||||
MetaSensitivemediadetectionEnum,
|
||||
MetaSensitivemediadetectionsensitivityEnum,
|
||||
MutedNoteReasonEnum,
|
||||
NoteVisibilityEnum,
|
||||
NotificationTypeEnum,
|
||||
|
@ -304,10 +302,6 @@ module.exports.EnvConfig = EnvConfig;
|
|||
module.exports.readEnvironmentConfig = readEnvironmentConfig;
|
||||
module.exports.readServerConfig = readServerConfig;
|
||||
module.exports.AntennaSrcEnum = AntennaSrcEnum;
|
||||
module.exports.MetaSensitivemediadetectionEnum =
|
||||
MetaSensitivemediadetectionEnum;
|
||||
module.exports.MetaSensitivemediadetectionsensitivityEnum =
|
||||
MetaSensitivemediadetectionsensitivityEnum;
|
||||
module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum;
|
||||
module.exports.NoteVisibilityEnum = NoteVisibilityEnum;
|
||||
module.exports.NotificationTypeEnum = NotificationTypeEnum;
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
-- remove-nsfw-detection
|
||||
ALTER TABLE "user_profile" ADD "autoSensitive" boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE "meta" ADD "enableSensitiveMediaDetectionForVideos" boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE "meta" ADD "setSensitiveFlagAutomatically" boolean NOT NULL DEFAULT false;
|
||||
CREATE TYPE "public"."meta_sensitivemediadetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh');
|
||||
ALTER TABLE "meta" ADD "sensitiveMediaDetectionSensitivity" "public"."meta_sensitivemediadetectionsensitivity_enum" NOT NULL DEFAULT 'medium';
|
||||
CREATE TYPE "public"."meta_sensitivemediadetection_enum" AS ENUM('none', 'all', 'local', 'remote');
|
||||
ALTER TABLE "meta" ADD "sensitiveMediaDetection" "public"."meta_sensitivemediadetection_enum" NOT NULL DEFAULT 'none';
|
||||
ALTER TABLE "drive_file" ADD "maybePorn" boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE "drive_file" ADD "maybeSensitive" boolean NOT NULL DEFAULT false;
|
||||
COMMENT ON COLUMN "drive_file"."maybeSensitive" IS 'Whether the DriveFile is NSFW. (predict)';
|
||||
|
||||
-- drop-time-zone
|
||||
ALTER TABLE "webhook" ALTER "latestSentAt" TYPE timestamp with time zone;
|
||||
ALTER TABLE "webhook" ALTER "createdAt" TYPE timestamp with time zone;
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"@bull-board/api": "5.13.0",
|
||||
"@bull-board/ui": "5.13.0",
|
||||
"@napi-rs/cli": "2.17.0",
|
||||
"@tensorflow/tfjs": "4.16.0",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-replace": "1.1.4",
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
export class RemoveNsfwDetection1705848938166 {
|
||||
name = "RemoveNsfwDetection1705848938166";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "drive_file" DROP COLUMN "maybeSensitive"`,
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "maybePorn"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" DROP COLUMN "sensitiveMediaDetection"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "public"."meta_sensitivemediadetection_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" DROP COLUMN "sensitiveMediaDetectionSensitivity"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "public"."meta_sensitivemediadetectionsensitivity_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" DROP COLUMN "setSensitiveFlagAutomatically"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" DROP COLUMN "enableSensitiveMediaDetectionForVideos"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_profile" DROP COLUMN "autoSensitive"`,
|
||||
);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_profile" ADD "autoSensitive" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" ADD "enableSensitiveMediaDetectionForVideos" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" ADD "setSensitiveFlagAutomatically" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "public"."meta_sensitivemediadetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" ADD "sensitiveMediaDetectionSensitivity" "public"."meta_sensitivemediadetectionsensitivity_enum" NOT NULL DEFAULT 'medium'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "public"."meta_sensitivemediadetection_enum" AS ENUM('none', 'all', 'local', 'remote')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" ADD "sensitiveMediaDetection" "public"."meta_sensitivemediadetection_enum" NOT NULL DEFAULT 'none'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "drive_file" ADD "maybePorn" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "drive_file" ADD "maybeSensitive" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`COMMENT ON COLUMN "drive_file"."maybeSensitive" IS 'Whether the DriveFile is NSFW. (predict)'`,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -49,10 +49,6 @@ pub struct Model {
|
|||
pub request_headers: Option<Json>,
|
||||
#[sea_orm(column_name = "requestIp")]
|
||||
pub request_ip: Option<String>,
|
||||
#[sea_orm(column_name = "maybeSensitive")]
|
||||
pub maybe_sensitive: bool,
|
||||
#[sea_orm(column_name = "maybePorn")]
|
||||
pub maybe_porn: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
|
||||
|
||||
use super::sea_orm_active_enums::MetaSensitivemediadetectionEnum;
|
||||
use super::sea_orm_active_enums::MetaSensitivemediadetectionsensitivityEnum;
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
|
@ -138,16 +136,8 @@ pub struct Model {
|
|||
pub default_light_theme: Option<String>,
|
||||
#[sea_orm(column_name = "defaultDarkTheme")]
|
||||
pub default_dark_theme: Option<String>,
|
||||
#[sea_orm(column_name = "sensitiveMediaDetection")]
|
||||
pub sensitive_media_detection: MetaSensitivemediadetectionEnum,
|
||||
#[sea_orm(column_name = "sensitiveMediaDetectionSensitivity")]
|
||||
pub sensitive_media_detection_sensitivity: MetaSensitivemediadetectionsensitivityEnum,
|
||||
#[sea_orm(column_name = "setSensitiveFlagAutomatically")]
|
||||
pub set_sensitive_flag_automatically: bool,
|
||||
#[sea_orm(column_name = "enableIpLogging")]
|
||||
pub enable_ip_logging: bool,
|
||||
#[sea_orm(column_name = "enableSensitiveMediaDetectionForVideos")]
|
||||
pub enable_sensitive_media_detection_for_videos: bool,
|
||||
#[sea_orm(column_name = "enableActiveEmailValidation")]
|
||||
pub enable_active_email_validation: bool,
|
||||
#[sea_orm(column_name = "customMOTD")]
|
||||
|
|
|
@ -21,42 +21,6 @@ pub enum AntennaSrcEnum {
|
|||
}
|
||||
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[napi_derive::napi]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "meta_sensitivemediadetection_enum"
|
||||
)]
|
||||
pub enum MetaSensitivemediadetectionEnum {
|
||||
#[sea_orm(string_value = "all")]
|
||||
All,
|
||||
#[sea_orm(string_value = "local")]
|
||||
Local,
|
||||
#[sea_orm(string_value = "none")]
|
||||
None,
|
||||
#[sea_orm(string_value = "remote")]
|
||||
Remote,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[napi_derive::napi]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "meta_sensitivemediadetectionsensitivity_enum"
|
||||
)]
|
||||
pub enum MetaSensitivemediadetectionsensitivityEnum {
|
||||
#[sea_orm(string_value = "high")]
|
||||
High,
|
||||
#[sea_orm(string_value = "low")]
|
||||
Low,
|
||||
#[sea_orm(string_value = "medium")]
|
||||
Medium,
|
||||
#[sea_orm(string_value = "veryHigh")]
|
||||
VeryHigh,
|
||||
#[sea_orm(string_value = "veryLow")]
|
||||
VeryLow,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[napi_derive::napi]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
|
|
|
@ -67,8 +67,6 @@ pub struct Model {
|
|||
pub public_reactions: bool,
|
||||
#[sea_orm(column_name = "ffVisibility")]
|
||||
pub ff_visibility: UserProfileFfvisibilityEnum,
|
||||
#[sea_orm(column_name = "autoSensitive")]
|
||||
pub auto_sensitive: bool,
|
||||
#[sea_orm(column_name = "moderationNote")]
|
||||
pub moderation_note: String,
|
||||
#[sea_orm(column_name = "preventAiLearning")]
|
||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
@ -20,8 +20,7 @@
|
|||
"format": "pnpm biome format * --write"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@tensorflow/tfjs-node": "4.16.0"
|
||||
"@swc/core-android-arm64": "1.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bull-board/api": "5.13.0",
|
||||
|
@ -35,7 +34,6 @@
|
|||
"@peertube/http-signature": "1.7.0",
|
||||
"@redocly/openapi-core": "1.6.0",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@tensorflow/tfjs": "^4.16.0",
|
||||
"@twemoji/parser": "^15.0.0",
|
||||
"adm-zip": "^0.5.10",
|
||||
"ajv": "8.12.0",
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import * as fs from "node:fs";
|
||||
import * as crypto from "node:crypto";
|
||||
import { join } from "node:path";
|
||||
import * as stream from "node:stream";
|
||||
import * as util from "node:util";
|
||||
import { FSWatcher } from "chokidar";
|
||||
import { fileTypeFromFile } from "file-type";
|
||||
import probeImageSize from "probe-image-size";
|
||||
import FFmpeg from "fluent-ffmpeg";
|
||||
import isSvg from "is-svg";
|
||||
import { type predictionType } from "nsfwjs";
|
||||
import sharp from "sharp";
|
||||
import { encode } from "blurhash";
|
||||
import { detectSensitive } from "@/services/detect-sensitive.js";
|
||||
import { createTempDir } from "./create-temp.js";
|
||||
|
||||
const pipeline = util.promisify(stream.pipeline);
|
||||
|
||||
|
@ -27,8 +21,6 @@ export type FileInfo = {
|
|||
height?: number;
|
||||
orientation?: number;
|
||||
blurhash?: string;
|
||||
sensitive: boolean;
|
||||
porn: boolean;
|
||||
warnings: string[];
|
||||
};
|
||||
|
||||
|
@ -45,15 +37,7 @@ const TYPE_SVG = {
|
|||
/**
|
||||
* Get file information
|
||||
*/
|
||||
export async function getFileInfo(
|
||||
path: string,
|
||||
opts: {
|
||||
skipSensitiveDetection: boolean;
|
||||
sensitiveThreshold?: number;
|
||||
sensitiveThresholdForPorn?: number;
|
||||
enableSensitiveMediaDetectionForVideos?: boolean;
|
||||
},
|
||||
): Promise<FileInfo> {
|
||||
export async function getFileInfo(path: string): Promise<FileInfo> {
|
||||
const warnings = [] as string[];
|
||||
|
||||
const size = await getFileSize(path);
|
||||
|
@ -123,26 +107,6 @@ export async function getFileInfo(
|
|||
});
|
||||
}
|
||||
|
||||
let sensitive = false;
|
||||
let porn = false;
|
||||
|
||||
if (!opts.skipSensitiveDetection) {
|
||||
await detectSensitivity(
|
||||
path,
|
||||
type.mime,
|
||||
opts.sensitiveThreshold ?? 0.5,
|
||||
opts.sensitiveThresholdForPorn ?? 0.75,
|
||||
opts.enableSensitiveMediaDetectionForVideos ?? false,
|
||||
).then(
|
||||
(value) => {
|
||||
[sensitive, porn] = value;
|
||||
},
|
||||
(error) => {
|
||||
warnings.push(`detectSensitivity failed: ${error}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
size,
|
||||
md5,
|
||||
|
@ -151,190 +115,10 @@ export async function getFileInfo(
|
|||
height,
|
||||
orientation,
|
||||
blurhash,
|
||||
sensitive,
|
||||
porn,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
async function detectSensitivity(
|
||||
source: string,
|
||||
mime: string,
|
||||
sensitiveThreshold: number,
|
||||
sensitiveThresholdForPorn: number,
|
||||
analyzeVideo: boolean,
|
||||
): Promise<[sensitive: boolean, porn: boolean]> {
|
||||
let sensitive = false;
|
||||
let porn = false;
|
||||
|
||||
function judgePrediction(
|
||||
result: readonly predictionType[],
|
||||
): [sensitive: boolean, porn: boolean] {
|
||||
let sensitive = false;
|
||||
let porn = false;
|
||||
|
||||
if (
|
||||
(result.find((x) => x.className === "Sexy")?.probability ?? 0) >
|
||||
sensitiveThreshold
|
||||
)
|
||||
sensitive = true;
|
||||
if (
|
||||
(result.find((x) => x.className === "Hentai")?.probability ?? 0) >
|
||||
sensitiveThreshold
|
||||
)
|
||||
sensitive = true;
|
||||
if (
|
||||
(result.find((x) => x.className === "Porn")?.probability ?? 0) >
|
||||
sensitiveThreshold
|
||||
)
|
||||
sensitive = true;
|
||||
|
||||
if (
|
||||
(result.find((x) => x.className === "Porn")?.probability ?? 0) >
|
||||
sensitiveThresholdForPorn
|
||||
)
|
||||
porn = true;
|
||||
|
||||
return [sensitive, porn];
|
||||
}
|
||||
|
||||
if (["image/jpeg", "image/png", "image/webp"].includes(mime)) {
|
||||
const result = await detectSensitive(source);
|
||||
if (result) {
|
||||
[sensitive, porn] = judgePrediction(result);
|
||||
}
|
||||
} else if (
|
||||
analyzeVideo &&
|
||||
(mime === "image/apng" || mime.startsWith("video/"))
|
||||
) {
|
||||
const [outDir, disposeOutDir] = await createTempDir();
|
||||
try {
|
||||
const command = FFmpeg()
|
||||
.input(source)
|
||||
.inputOptions([
|
||||
"-skip_frame",
|
||||
"nokey", // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
|
||||
"-lowres",
|
||||
"3", // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
|
||||
])
|
||||
.noAudio()
|
||||
.videoFilters([
|
||||
{
|
||||
filter: "select", // フレームのフィルタリング
|
||||
options: {
|
||||
e: "eq(pict_type,PICT_TYPE_I)", // I-Frame のみをフィルタする(VP9 とかはデコードしてみないとわからないっぽい)
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: "blackframe", // 暗いフレームの検出
|
||||
options: {
|
||||
amount: "0", // 暗さに関わらず全てのフレームで測定値を取る
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: "metadata",
|
||||
options: {
|
||||
mode: "select", // フレーム選択モード
|
||||
key: "lavfi.blackframe.pblack", // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
|
||||
value: "50",
|
||||
function: "less", // 50% 未満のフレームを選択する(50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: "scale",
|
||||
options: {
|
||||
w: 299,
|
||||
h: 299,
|
||||
},
|
||||
},
|
||||
])
|
||||
.format("image2")
|
||||
.output(join(outDir, "%d.png"))
|
||||
.outputOptions(["-vsync", "0"]); // 可変フレームレートにすることで穴埋めをさせない
|
||||
const results: ReturnType<typeof judgePrediction>[] = [];
|
||||
let frameIndex = 0;
|
||||
let targetIndex = 0;
|
||||
let nextIndex = 1;
|
||||
for await (const path of asyncIterateFrames(outDir, command)) {
|
||||
try {
|
||||
const index = frameIndex++;
|
||||
if (index !== targetIndex) {
|
||||
continue;
|
||||
}
|
||||
targetIndex = nextIndex;
|
||||
nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける
|
||||
const result = await detectSensitive(path);
|
||||
if (result) {
|
||||
results.push(judgePrediction(result));
|
||||
}
|
||||
} finally {
|
||||
fs.promises.unlink(path);
|
||||
}
|
||||
}
|
||||
sensitive =
|
||||
results.filter((x) => x[0]).length >=
|
||||
Math.ceil(results.length * sensitiveThreshold);
|
||||
porn =
|
||||
results.filter((x) => x[1]).length >=
|
||||
Math.ceil(results.length * sensitiveThresholdForPorn);
|
||||
} finally {
|
||||
disposeOutDir();
|
||||
}
|
||||
}
|
||||
|
||||
return [sensitive, porn];
|
||||
}
|
||||
|
||||
async function* asyncIterateFrames(
|
||||
cwd: string,
|
||||
command: FFmpeg.FfmpegCommand,
|
||||
): AsyncGenerator<string, void> {
|
||||
const watcher = new FSWatcher({
|
||||
cwd,
|
||||
disableGlobbing: true,
|
||||
});
|
||||
let finished = false;
|
||||
command.once("end", () => {
|
||||
finished = true;
|
||||
watcher.close();
|
||||
});
|
||||
command.run();
|
||||
for (let i = 1; true; i++) {
|
||||
const current = `${i}.png`;
|
||||
const next = `${i + 1}.png`;
|
||||
const framePath = join(cwd, current);
|
||||
if (await exists(join(cwd, next))) {
|
||||
yield framePath;
|
||||
} else if (!finished) {
|
||||
watcher.add(next);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
watcher.on("add", function onAdd(path) {
|
||||
if (path === next) {
|
||||
// 次フレームの書き出しが始まっているなら、現在フレームの書き出しは終わっている
|
||||
watcher.unwatch(current);
|
||||
watcher.off("add", onAdd);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
command.once("end", resolve); // 全てのフレームを処理し終わったなら、最終フレームである現在フレームの書き出しは終わっている
|
||||
command.once("error", reject);
|
||||
});
|
||||
yield framePath;
|
||||
} else if (await exists(framePath)) {
|
||||
yield framePath;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function exists(path: string): Promise<boolean> {
|
||||
return fs.promises.access(path).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect MIME Type and extension
|
||||
*/
|
||||
|
|
|
@ -183,19 +183,6 @@ export class DriveFile {
|
|||
})
|
||||
public isSensitive: boolean;
|
||||
|
||||
@Index()
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
comment: "Whether the DriveFile is NSFW. (predict)",
|
||||
})
|
||||
public maybeSensitive: boolean;
|
||||
|
||||
@Index()
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
public maybePorn: boolean;
|
||||
|
||||
/**
|
||||
* 外部の(信頼されていない)URLへの直リンクか否か
|
||||
*/
|
||||
|
|
|
@ -258,33 +258,6 @@ export class Meta {
|
|||
})
|
||||
public recaptchaSecretKey: string | null;
|
||||
|
||||
@Column("enum", {
|
||||
enum: ["none", "all", "local", "remote"],
|
||||
default: "none",
|
||||
})
|
||||
public sensitiveMediaDetection: "none" | "all" | "local" | "remote";
|
||||
|
||||
@Column("enum", {
|
||||
enum: ["medium", "low", "high", "veryLow", "veryHigh"],
|
||||
default: "medium",
|
||||
})
|
||||
public sensitiveMediaDetectionSensitivity:
|
||||
| "medium"
|
||||
| "low"
|
||||
| "high"
|
||||
| "veryLow"
|
||||
| "veryHigh";
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
public setSensitiveFlagAutomatically: boolean;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
public enableSensitiveMediaDetectionForVideos: boolean;
|
||||
|
||||
@Column("integer", {
|
||||
default: 1024,
|
||||
comment: "Drive capacity of a local user (MB)",
|
||||
|
|
|
@ -184,11 +184,6 @@ export class UserProfile {
|
|||
})
|
||||
public alwaysMarkNsfw: boolean;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
public autoSensitive: boolean;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
|
|
|
@ -548,7 +548,6 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
injectFeaturedNote: profile?.injectFeaturedNote,
|
||||
receiveAnnouncementEmail: profile?.receiveAnnouncementEmail,
|
||||
alwaysMarkNsfw: profile?.alwaysMarkNsfw,
|
||||
autoSensitive: profile?.autoSensitive,
|
||||
carefulBot: profile?.carefulBot,
|
||||
autoAcceptFollowed: profile?.autoAcceptFollowed,
|
||||
noCrawle: profile?.noCrawle,
|
||||
|
|
|
@ -384,11 +384,6 @@ export const packedMeDetailedOnlySchema = {
|
|||
nullable: true,
|
||||
optional: false,
|
||||
},
|
||||
autoSensitive: {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
optional: false,
|
||||
},
|
||||
carefulBot: {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
|
|
|
@ -460,7 +460,7 @@ export const paramDef = {
|
|||
required: [],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
export default define(meta, paramDef, async () => {
|
||||
const instance = await fetchMeta(true);
|
||||
|
||||
return {
|
||||
|
@ -519,12 +519,10 @@ export default define(meta, paramDef, async (ps) => {
|
|||
secureMode: instance.secureMode,
|
||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||
recaptchaSecretKey: instance.recaptchaSecretKey,
|
||||
sensitiveMediaDetection: instance.sensitiveMediaDetection,
|
||||
sensitiveMediaDetectionSensitivity:
|
||||
instance.sensitiveMediaDetectionSensitivity,
|
||||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||
enableSensitiveMediaDetectionForVideos:
|
||||
instance.enableSensitiveMediaDetectionForVideos,
|
||||
sensitiveMediaDetection: false, // for compatibility
|
||||
sensitiveMediaDetectionSensitivity: "medium", // for compatibility
|
||||
setSensitiveFlagAutomatically: false, // for compatibility
|
||||
enableSensitiveMediaDetectionForVideos: false, // for compatibility
|
||||
proxyAccountId: instance.proxyAccountId,
|
||||
summalyProxy: instance.summalyProxy,
|
||||
email: instance.email,
|
||||
|
|
|
@ -56,7 +56,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
isIndexable: profile.isIndexable,
|
||||
preventAiLearning: profile.preventAiLearning,
|
||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||
autoSensitive: profile.autoSensitive,
|
||||
autoSensitive: false, // for compatibility
|
||||
carefulBot: profile.carefulBot,
|
||||
injectFeaturedNote: profile.injectFeaturedNote,
|
||||
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
||||
|
|
|
@ -102,16 +102,6 @@ export const paramDef = {
|
|||
enableRecaptcha: { type: "boolean" },
|
||||
recaptchaSiteKey: { type: "string", nullable: true },
|
||||
recaptchaSecretKey: { type: "string", nullable: true },
|
||||
sensitiveMediaDetection: {
|
||||
type: "string",
|
||||
enum: ["none", "all", "local", "remote"],
|
||||
},
|
||||
sensitiveMediaDetectionSensitivity: {
|
||||
type: "string",
|
||||
enum: ["medium", "low", "high", "veryLow", "veryHigh"],
|
||||
},
|
||||
setSensitiveFlagAutomatically: { type: "boolean" },
|
||||
enableSensitiveMediaDetectionForVideos: { type: "boolean" },
|
||||
proxyAccountId: { type: "string", format: "misskey:id", nullable: true },
|
||||
maintainerName: { type: "string", nullable: true },
|
||||
maintainerEmail: { type: "string", nullable: true },
|
||||
|
@ -368,24 +358,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
set.recaptchaSecretKey = ps.recaptchaSecretKey;
|
||||
}
|
||||
|
||||
if (ps.sensitiveMediaDetection !== undefined) {
|
||||
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
|
||||
}
|
||||
|
||||
if (ps.sensitiveMediaDetectionSensitivity !== undefined) {
|
||||
set.sensitiveMediaDetectionSensitivity =
|
||||
ps.sensitiveMediaDetectionSensitivity;
|
||||
}
|
||||
|
||||
if (ps.setSensitiveFlagAutomatically !== undefined) {
|
||||
set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically;
|
||||
}
|
||||
|
||||
if (ps.enableSensitiveMediaDetectionForVideos !== undefined) {
|
||||
set.enableSensitiveMediaDetectionForVideos =
|
||||
ps.enableSensitiveMediaDetectionForVideos;
|
||||
}
|
||||
|
||||
if (ps.proxyAccountId !== undefined) {
|
||||
set.proxyAccountId = ps.proxyAccountId;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,6 @@ export const paramDef = {
|
|||
injectFeaturedNote: { type: "boolean" },
|
||||
receiveAnnouncementEmail: { type: "boolean" },
|
||||
alwaysMarkNsfw: { type: "boolean" },
|
||||
autoSensitive: { type: "boolean" },
|
||||
ffVisibility: { type: "string", enum: ["public", "followers", "private"] },
|
||||
pinnedPageId: { type: "string", format: "misskey:id", nullable: true },
|
||||
mutedWords: { type: "array" },
|
||||
|
@ -217,8 +216,6 @@ export default define(meta, paramDef, async (ps, _user, token) => {
|
|||
profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||
if (typeof ps.alwaysMarkNsfw === "boolean")
|
||||
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||
if (typeof ps.autoSensitive === "boolean")
|
||||
profileUpdates.autoSensitive = ps.autoSensitive;
|
||||
if (ps.emailNotificationTypes !== undefined)
|
||||
profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
||||
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
import * as fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname } from "node:path";
|
||||
import * as nsfw from "nsfwjs";
|
||||
import si from "systeminformation";
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const REQUIRED_CPU_FLAGS = ["avx2", "fma"];
|
||||
let isSupportedCpu: undefined | boolean = undefined;
|
||||
|
||||
let model: nsfw.NSFWJS;
|
||||
|
||||
export async function detectSensitive(
|
||||
path: string,
|
||||
): Promise<nsfw.predictionType[] | null> {
|
||||
try {
|
||||
if (isSupportedCpu === undefined) {
|
||||
const cpuFlags = await getCpuFlags();
|
||||
isSupportedCpu = REQUIRED_CPU_FLAGS.every((required) =>
|
||||
cpuFlags.includes(required),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isSupportedCpu) {
|
||||
console.error("This CPU cannot use TensorFlow.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const tf = await import("@tensorflow/tfjs-node");
|
||||
|
||||
if (model == null)
|
||||
model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, {
|
||||
size: 299,
|
||||
});
|
||||
|
||||
const buffer = await fs.promises.readFile(path);
|
||||
const image = (await tf.node.decodeImage(buffer, 3)) as any;
|
||||
try {
|
||||
const predictions = await model.classify(image);
|
||||
return predictions;
|
||||
} finally {
|
||||
image.dispose();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getCpuFlags(): Promise<string[]> {
|
||||
const str = await si.cpuFlags();
|
||||
return str.split(/\s+/);
|
||||
}
|
|
@ -469,46 +469,9 @@ export async function addFile({
|
|||
requestIp = null,
|
||||
requestHeaders = null,
|
||||
}: AddFileArgs): Promise<DriveFile> {
|
||||
let skipNsfwCheck = false;
|
||||
const instance = await fetchMeta();
|
||||
if (user == null) skipNsfwCheck = true;
|
||||
if (instance.sensitiveMediaDetection === "none") skipNsfwCheck = true;
|
||||
if (
|
||||
user &&
|
||||
instance.sensitiveMediaDetection === "local" &&
|
||||
Users.isRemoteUser(user)
|
||||
)
|
||||
skipNsfwCheck = true;
|
||||
if (
|
||||
user &&
|
||||
instance.sensitiveMediaDetection === "remote" &&
|
||||
Users.isLocalUser(user)
|
||||
)
|
||||
skipNsfwCheck = true;
|
||||
|
||||
const info = await getFileInfo(path, {
|
||||
skipSensitiveDetection: skipNsfwCheck,
|
||||
sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる
|
||||
instance.sensitiveMediaDetectionSensitivity === "veryHigh"
|
||||
? 0.1
|
||||
: instance.sensitiveMediaDetectionSensitivity === "high"
|
||||
? 0.3
|
||||
: instance.sensitiveMediaDetectionSensitivity === "low"
|
||||
? 0.7
|
||||
: instance.sensitiveMediaDetectionSensitivity === "veryLow"
|
||||
? 0.9
|
||||
: 0.5,
|
||||
sensitiveThresholdForPorn: 0.75,
|
||||
enableSensitiveMediaDetectionForVideos:
|
||||
instance.enableSensitiveMediaDetectionForVideos,
|
||||
});
|
||||
const info = await getFileInfo(path);
|
||||
logger.info(`${JSON.stringify(info)}`);
|
||||
|
||||
// 現状 false positive が多すぎて実用に耐えない
|
||||
//if (info.porn && instance.disallowUploadWhenPredictedAsPorn) {
|
||||
// throw new IdentifiableError('282f77bf-5816-4f72-9264-aa14d8261a21', 'Detected as porn.');
|
||||
//}
|
||||
|
||||
// detect name
|
||||
const detectedName =
|
||||
name || (info.type.ext ? `untitled.${info.type.ext}` : "untitled");
|
||||
|
@ -616,8 +579,6 @@ export async function addFile({
|
|||
file.isLink = isLink;
|
||||
file.requestIp = requestIp;
|
||||
file.requestHeaders = requestHeaders;
|
||||
file.maybeSensitive = info.sensitive;
|
||||
file.maybePorn = info.porn;
|
||||
file.isSensitive = user
|
||||
? Users.isLocalUser(user) && profile?.alwaysMarkNsfw
|
||||
? true
|
||||
|
@ -626,10 +587,6 @@ export async function addFile({
|
|||
: false
|
||||
: false;
|
||||
|
||||
if (info.sensitive && profile?.autoSensitive) file.isSensitive = true;
|
||||
if (info.sensitive && instance.setSensitiveFlagAutomatically)
|
||||
file.isSensitive = true;
|
||||
|
||||
if (url !== null) {
|
||||
file.src = url;
|
||||
|
||||
|
|
|
@ -29,114 +29,6 @@
|
|||
<XBotProtection />
|
||||
</FormFolder>
|
||||
|
||||
<FormFolder class="_formBlock">
|
||||
<template #icon
|
||||
><i :class="icon('ph-eye-slash')"></i
|
||||
></template>
|
||||
<template #label>{{
|
||||
i18n.ts.sensitiveMediaDetection
|
||||
}}</template>
|
||||
<template
|
||||
v-if="sensitiveMediaDetection === 'all'"
|
||||
#suffix
|
||||
>{{ i18n.ts.all }}</template
|
||||
>
|
||||
<template
|
||||
v-else-if="sensitiveMediaDetection === 'local'"
|
||||
#suffix
|
||||
>{{ i18n.ts.localOnly }}</template
|
||||
>
|
||||
<template
|
||||
v-else-if="sensitiveMediaDetection === 'remote'"
|
||||
#suffix
|
||||
>{{ i18n.ts.remoteOnly }}</template
|
||||
>
|
||||
<template v-else #suffix>{{ i18n.ts.none }}</template>
|
||||
|
||||
<div class="_formRoot">
|
||||
<span class="_formBlock">{{
|
||||
i18n.ts._sensitiveMediaDetection.description
|
||||
}}</span>
|
||||
|
||||
<FormRadios
|
||||
v-model="sensitiveMediaDetection"
|
||||
class="_formBlock"
|
||||
>
|
||||
<option value="none">{{ i18n.ts.none }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="local">
|
||||
{{ i18n.ts.localOnly }}
|
||||
</option>
|
||||
<option value="remote">
|
||||
{{ i18n.ts.remoteOnly }}
|
||||
</option>
|
||||
</FormRadios>
|
||||
|
||||
<FormRange
|
||||
v-model="sensitiveMediaDetectionSensitivity"
|
||||
:min="0"
|
||||
:max="4"
|
||||
:step="1"
|
||||
:text-converter="(v) => `${v + 1}`"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #label>{{
|
||||
i18n.ts._sensitiveMediaDetection.sensitivity
|
||||
}}</template>
|
||||
<template #caption>{{
|
||||
i18n.ts._sensitiveMediaDetection
|
||||
.sensitivityDescription
|
||||
}}</template>
|
||||
</FormRange>
|
||||
|
||||
<FormSwitch
|
||||
v-model="enableSensitiveMediaDetectionForVideos"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #label
|
||||
>{{
|
||||
i18n.ts._sensitiveMediaDetection
|
||||
.analyzeVideos
|
||||
}}<span class="_beta">{{
|
||||
i18n.ts.beta
|
||||
}}</span></template
|
||||
>
|
||||
<template #caption>{{
|
||||
i18n.ts._sensitiveMediaDetection
|
||||
.analyzeVideosDescription
|
||||
}}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSwitch
|
||||
v-model="setSensitiveFlagAutomatically"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #label
|
||||
>{{
|
||||
i18n.ts._sensitiveMediaDetection
|
||||
.setSensitiveFlagAutomatically
|
||||
}}
|
||||
({{ i18n.ts.notRecommended }})</template
|
||||
>
|
||||
<template #caption>{{
|
||||
i18n.ts._sensitiveMediaDetection
|
||||
.setSensitiveFlagAutomaticallyDescription
|
||||
}}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<!-- 現状 false positive が多すぎて実用に耐えない
|
||||
<FormSwitch v-model="disallowUploadWhenPredictedAsPorn" class="_formBlock">
|
||||
<template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template>
|
||||
</FormSwitch>
|
||||
-->
|
||||
|
||||
<FormButton primary class="_formBlock" @click="save"
|
||||
><i :class="icon('ph-floppy-disk-back')"></i>
|
||||
{{ i18n.ts.save }}</FormButton
|
||||
>
|
||||
</div>
|
||||
</FormFolder>
|
||||
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>Active Email Validation</template>
|
||||
<template v-if="enableActiveEmailValidation" #suffix
|
||||
|
@ -253,10 +145,8 @@ import { computed, ref } from "vue";
|
|||
|
||||
import XBotProtection from "./bot-protection.vue";
|
||||
import FormFolder from "@/components/form/folder.vue";
|
||||
import FormRadios from "@/components/form/radios.vue";
|
||||
import FormSwitch from "@/components/form/switch.vue";
|
||||
import FormSuspense from "@/components/form/suspense.vue";
|
||||
import FormRange from "@/components/form/range.vue";
|
||||
import FormInput from "@/components/form/input.vue";
|
||||
import FormTextarea from "@/components/form/textarea.vue";
|
||||
import FormButton from "@/components/MkButton.vue";
|
||||
|
@ -269,10 +159,6 @@ import icon from "@/scripts/icon";
|
|||
const summalyProxy = ref("");
|
||||
const enableHcaptcha = ref(false);
|
||||
const enableRecaptcha = ref(false);
|
||||
const sensitiveMediaDetection = ref("none");
|
||||
const sensitiveMediaDetectionSensitivity = ref(0);
|
||||
const setSensitiveFlagAutomatically = ref(false);
|
||||
const enableSensitiveMediaDetectionForVideos = ref(false);
|
||||
const enableIpLogging = ref(false);
|
||||
const enableActiveEmailValidation = ref(false);
|
||||
|
||||
|
@ -285,22 +171,6 @@ async function init() {
|
|||
summalyProxy.value = meta.summalyProxy;
|
||||
enableHcaptcha.value = meta.enableHcaptcha;
|
||||
enableRecaptcha.value = meta.enableRecaptcha;
|
||||
sensitiveMediaDetection.value = meta.sensitiveMediaDetection;
|
||||
sensitiveMediaDetectionSensitivity.value =
|
||||
meta.sensitiveMediaDetectionSensitivity === "veryLow"
|
||||
? 0
|
||||
: meta.sensitiveMediaDetectionSensitivity === "low"
|
||||
? 1
|
||||
: meta.sensitiveMediaDetectionSensitivity === "medium"
|
||||
? 2
|
||||
: meta.sensitiveMediaDetectionSensitivity === "high"
|
||||
? 3
|
||||
: meta.sensitiveMediaDetectionSensitivity === "veryHigh"
|
||||
? 4
|
||||
: 0;
|
||||
setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically;
|
||||
enableSensitiveMediaDetectionForVideos.value =
|
||||
meta.enableSensitiveMediaDetectionForVideos;
|
||||
enableIpLogging.value = meta.enableIpLogging;
|
||||
enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
|
||||
|
||||
|
@ -312,22 +182,6 @@ async function init() {
|
|||
function save() {
|
||||
os.apiWithDialog("admin/update-meta", {
|
||||
summalyProxy: summalyProxy.value,
|
||||
sensitiveMediaDetection: sensitiveMediaDetection.value,
|
||||
sensitiveMediaDetectionSensitivity:
|
||||
sensitiveMediaDetectionSensitivity.value === 0
|
||||
? "veryLow"
|
||||
: sensitiveMediaDetectionSensitivity.value === 1
|
||||
? "low"
|
||||
: sensitiveMediaDetectionSensitivity.value === 2
|
||||
? "medium"
|
||||
: sensitiveMediaDetectionSensitivity.value === 3
|
||||
? "high"
|
||||
: sensitiveMediaDetectionSensitivity.value === 4
|
||||
? "veryHigh"
|
||||
: 0,
|
||||
setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value,
|
||||
enableSensitiveMediaDetectionForVideos:
|
||||
enableSensitiveMediaDetectionForVideos.value,
|
||||
enableIpLogging: enableIpLogging.value,
|
||||
enableActiveEmailValidation: enableActiveEmailValidation.value,
|
||||
}).then(() => {
|
||||
|
|
|
@ -40,19 +40,6 @@
|
|||
>
|
||||
<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch
|
||||
v-model="autoSensitive"
|
||||
class="_formBlock"
|
||||
@update:modelValue="saveProfile()"
|
||||
>
|
||||
<template #label
|
||||
>{{ i18n.ts.enableAutoSensitive
|
||||
}}<span class="_beta">{{ i18n.ts.beta }}</span></template
|
||||
>
|
||||
<template #caption>{{
|
||||
i18n.ts.enableAutoSensitiveDescription
|
||||
}}</template>
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -78,7 +65,6 @@ const usage = ref<any>(null);
|
|||
const capacity = ref<any>(null);
|
||||
const uploadFolder = ref<any>(null);
|
||||
const alwaysMarkNsfw = ref<boolean>(isSignedIn && $i.alwaysMarkNsfw);
|
||||
const autoSensitive = ref<boolean>(isSignedIn && $i.autoSensitive);
|
||||
|
||||
const meterStyle = computed(() => {
|
||||
return {
|
||||
|
@ -126,7 +112,6 @@ function chooseUploadFolder() {
|
|||
function saveProfile() {
|
||||
os.api("i/update", {
|
||||
alwaysMarkNsfw: !!alwaysMarkNsfw.value,
|
||||
autoSensitive: !!autoSensitive.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
251
pnpm-lock.yaml
251
pnpm-lock.yaml
|
@ -17,9 +17,6 @@ importers:
|
|||
'@napi-rs/cli':
|
||||
specifier: 2.17.0
|
||||
version: 2.17.0
|
||||
'@tensorflow/tfjs':
|
||||
specifier: 4.16.0
|
||||
version: 4.16.0(seedrandom@3.0.5)
|
||||
gulp:
|
||||
specifier: 4.0.2
|
||||
version: 4.0.2
|
||||
|
@ -99,9 +96,6 @@ importers:
|
|||
'@sinonjs/fake-timers':
|
||||
specifier: 11.2.2
|
||||
version: 11.2.2
|
||||
'@tensorflow/tfjs':
|
||||
specifier: ^4.16.0
|
||||
version: 4.16.0(seedrandom@3.0.5)
|
||||
'@twemoji/parser':
|
||||
specifier: ^15.0.0
|
||||
version: 15.0.0
|
||||
|
@ -275,7 +269,7 @@ importers:
|
|||
version: 6.9.8
|
||||
nsfwjs:
|
||||
specifier: 2.4.2
|
||||
version: 2.4.2(@tensorflow/tfjs@4.16.0)
|
||||
version: 2.4.2(@tensorflow/tfjs@3.21.0)
|
||||
opencc-js:
|
||||
specifier: 1.0.5
|
||||
version: 1.0.5
|
||||
|
@ -391,9 +385,6 @@ importers:
|
|||
'@swc/core-android-arm64':
|
||||
specifier: 1.3.11
|
||||
version: 1.3.11
|
||||
'@tensorflow/tfjs-node':
|
||||
specifier: 4.16.0
|
||||
version: 4.16.0(seedrandom@3.0.5)
|
||||
devDependencies:
|
||||
'@swc/cli':
|
||||
specifier: 0.1.63
|
||||
|
@ -2934,26 +2925,6 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@mapbox/node-pre-gyp@1.0.9:
|
||||
resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
detect-libc: 2.0.2
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.7.0
|
||||
nopt: 5.0.0
|
||||
npmlog: 5.0.1
|
||||
rimraf: 3.0.2
|
||||
semver: 7.5.4
|
||||
tar: 6.2.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@microsoft/tsdoc-config@0.16.2:
|
||||
resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==}
|
||||
dependencies:
|
||||
|
@ -3515,46 +3486,49 @@ packages:
|
|||
defer-to-connect: 2.0.1
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-backend-cpu@4.16.0(@tensorflow/tfjs-core@4.16.0):
|
||||
resolution: {integrity: sha512-bQFu7FTUgqgss1AwnqSwQ1f02IPrfLLc2lLn5pyyVrS6Ex7zA6Y4YkfktqoJSRE6LlRZv3vxSriUGE1avRe4qQ==}
|
||||
/@tensorflow/tfjs-backend-cpu@3.21.0(@tensorflow/tfjs-core@3.21.0):
|
||||
resolution: {integrity: sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==}
|
||||
engines: {yarn: '>= 1.3.2'}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
'@types/seedrandom': 2.4.34
|
||||
seedrandom: 3.0.5
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-backend-webgl@4.16.0(@tensorflow/tfjs-core@4.16.0):
|
||||
resolution: {integrity: sha512-cIGZWuY892iwTRokbDj3qsLi0AlpQn+U7rzB1mddhHrWr9kBXrrnAvIq0h2aiFzRFNePWUcsbgK+HmYG32kosg==}
|
||||
/@tensorflow/tfjs-backend-webgl@3.21.0(@tensorflow/tfjs-core@3.21.0):
|
||||
resolution: {integrity: sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==}
|
||||
engines: {yarn: '>= 1.3.2'}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-backend-cpu': 4.16.0(@tensorflow/tfjs-core@4.16.0)
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-backend-cpu': 3.21.0(@tensorflow/tfjs-core@3.21.0)
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
'@types/offscreencanvas': 2019.3.0
|
||||
'@types/seedrandom': 2.4.34
|
||||
'@types/webgl-ext': 0.0.30
|
||||
'@types/webgl2': 0.0.6
|
||||
seedrandom: 3.0.5
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-converter@4.16.0(@tensorflow/tfjs-core@4.16.0):
|
||||
resolution: {integrity: sha512-gd8dHl9tqEPQOHZLAUza713nKr42rpvUXrtm7yUhk10THvJT6TXe9Q2AJKmni8J3vfR+ghsCh77F8D4RbShx1Q==}
|
||||
/@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@3.21.0):
|
||||
resolution: {integrity: sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-core@4.16.0:
|
||||
resolution: {integrity: sha512-MarAtO+Up6wA8pI9QDpQOwwJgb/imYMN++tsoaalyOEE9+B5HS4lQldxDJKXO8Frf4DyXf4FItJktEXaiPfRHw==}
|
||||
/@tensorflow/tfjs-core@3.21.0:
|
||||
resolution: {integrity: sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==}
|
||||
engines: {yarn: '>= 1.3.2'}
|
||||
dependencies:
|
||||
'@types/long': 4.0.2
|
||||
'@types/offscreencanvas': 2019.7.3
|
||||
'@types/offscreencanvas': 2019.3.0
|
||||
'@types/seedrandom': 2.4.34
|
||||
'@webgpu/types': 0.1.38
|
||||
'@types/webgl-ext': 0.0.30
|
||||
'@webgpu/types': 0.1.16
|
||||
long: 4.0.0
|
||||
node-fetch: 2.6.13
|
||||
seedrandom: 3.0.5
|
||||
|
@ -3562,13 +3536,13 @@ packages:
|
|||
- encoding
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-data@4.16.0(@tensorflow/tfjs-core@4.16.0)(seedrandom@3.0.5):
|
||||
resolution: {integrity: sha512-HAmB4/3mvR1t/fuxu4Vx7hEGb4w8EcJoPNlzRHLr0+cYOApii6HQ/OksCcp7Ll8JoCb/SruVR3En3WSjSUu8YQ==}
|
||||
/@tensorflow/tfjs-data@3.21.0(@tensorflow/tfjs-core@3.21.0)(seedrandom@3.0.5):
|
||||
resolution: {integrity: sha512-eFLfw2wIcFNxnP2Iv/SnVlihehzKMumk1b5Prcx1ixk/SbkCo5u0Lt7OVOWaEOKVqvB2sT+dJcTjAh6lrCC/QA==}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
seedrandom: ^3.0.5
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
'@types/node-fetch': 2.6.11
|
||||
node-fetch: 2.6.13
|
||||
seedrandom: 3.0.5
|
||||
|
@ -3577,47 +3551,27 @@ packages:
|
|||
- encoding
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-layers@4.16.0(@tensorflow/tfjs-core@4.16.0):
|
||||
resolution: {integrity: sha512-LNsbIF3SX45gG2FGo/34IPU+ObRCY5Z2znnp8cSqNZ96v52Q3nZ8GbyjYU9xDHv7eREKTSC2Aga2eGZ9Hfsl/g==}
|
||||
/@tensorflow/tfjs-layers@3.21.0(@tensorflow/tfjs-core@3.21.0):
|
||||
resolution: {integrity: sha512-CMVXsraakXgnXEnqD9QbtResA7nvV7Jz20pGmjFIodcQkClgmFFhdCG5N+zlVRHEz7VKG2OyfhltZ0dBq/OAhA==}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
dev: false
|
||||
|
||||
/@tensorflow/tfjs-node@4.16.0(seedrandom@3.0.5):
|
||||
resolution: {integrity: sha512-cjGaZzxTAHlGbXmmoyWcNVUcYb4urWnhaJ69zMdYlfdVkT1ShhopVjP1p1S9V93w2DqMn/fndAMICbj+LWBVeg==}
|
||||
engines: {node: '>=8.11.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.9
|
||||
'@tensorflow/tfjs': 4.16.0(seedrandom@3.0.5)
|
||||
adm-zip: 0.5.10
|
||||
google-protobuf: 3.21.2
|
||||
https-proxy-agent: 2.2.4
|
||||
progress: 2.0.3
|
||||
rimraf: 2.7.1
|
||||
tar: 4.4.19
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- seedrandom
|
||||
- supports-color
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@tensorflow/tfjs@4.16.0(seedrandom@3.0.5):
|
||||
resolution: {integrity: sha512-wBs29w1rsE6XpKdwJ7vrxGJ+nbyU7+51Pgj1ZhLFdv5ZXwF5irHbNX1DegSd/8VbHPEY6mQSPvEuSe1mIhMw5Q==}
|
||||
/@tensorflow/tfjs@3.21.0(seedrandom@3.0.5):
|
||||
resolution: {integrity: sha512-khcARd3/872llL/oF4ouR40qlT71mylU66PGT8kHP/GJ5YKj44sv8lDRjU7lOVlJK7jsJFWEsNVHI3eMc/GWNQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@tensorflow/tfjs-backend-cpu': 4.16.0(@tensorflow/tfjs-core@4.16.0)
|
||||
'@tensorflow/tfjs-backend-webgl': 4.16.0(@tensorflow/tfjs-core@4.16.0)
|
||||
'@tensorflow/tfjs-converter': 4.16.0(@tensorflow/tfjs-core@4.16.0)
|
||||
'@tensorflow/tfjs-core': 4.16.0
|
||||
'@tensorflow/tfjs-data': 4.16.0(@tensorflow/tfjs-core@4.16.0)(seedrandom@3.0.5)
|
||||
'@tensorflow/tfjs-layers': 4.16.0(@tensorflow/tfjs-core@4.16.0)
|
||||
'@tensorflow/tfjs-backend-cpu': 3.21.0(@tensorflow/tfjs-core@3.21.0)
|
||||
'@tensorflow/tfjs-backend-webgl': 3.21.0(@tensorflow/tfjs-core@3.21.0)
|
||||
'@tensorflow/tfjs-converter': 3.21.0(@tensorflow/tfjs-core@3.21.0)
|
||||
'@tensorflow/tfjs-core': 3.21.0
|
||||
'@tensorflow/tfjs-data': 3.21.0(@tensorflow/tfjs-core@3.21.0)(seedrandom@3.0.5)
|
||||
'@tensorflow/tfjs-layers': 3.21.0(@tensorflow/tfjs-core@3.21.0)
|
||||
argparse: 1.0.10
|
||||
chalk: 4.1.2
|
||||
core-js: 3.29.1
|
||||
core-js: 3.33.2
|
||||
regenerator-runtime: 0.13.11
|
||||
yargs: 16.2.0
|
||||
transitivePeerDependencies:
|
||||
|
@ -4064,10 +4018,6 @@ packages:
|
|||
resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
|
||||
dev: false
|
||||
|
||||
/@types/offscreencanvas@2019.7.3:
|
||||
resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
|
||||
dev: false
|
||||
|
||||
/@types/opencc-js@1.0.3:
|
||||
resolution: {integrity: sha512-TENp7YkI2hNlc4dplhivSHj0hU4DORCK56VY7rniaSfA5f87uD3uv+kPIRuH9h64TGv976iVFi4gEHZZtS2y8Q==}
|
||||
dev: true
|
||||
|
@ -4245,6 +4195,14 @@ packages:
|
|||
'@types/node': 20.11.5
|
||||
dev: true
|
||||
|
||||
/@types/webgl-ext@0.0.30:
|
||||
resolution: {integrity: sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==}
|
||||
dev: false
|
||||
|
||||
/@types/webgl2@0.0.6:
|
||||
resolution: {integrity: sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==}
|
||||
dev: false
|
||||
|
||||
/@types/websocket@1.0.10:
|
||||
resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==}
|
||||
dependencies:
|
||||
|
@ -4846,8 +4804,8 @@ packages:
|
|||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@webgpu/types@0.1.38:
|
||||
resolution: {integrity: sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==}
|
||||
/@webgpu/types@0.1.16:
|
||||
resolution: {integrity: sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==}
|
||||
dev: false
|
||||
|
||||
/@xtuc/ieee754@1.2.0:
|
||||
|
@ -4918,15 +4876,6 @@ packages:
|
|||
engines: {node: '>=6.0'}
|
||||
dev: false
|
||||
|
||||
/agent-base@4.3.0:
|
||||
resolution: {integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
es6-promisify: 5.0.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
@ -6267,12 +6216,6 @@ packages:
|
|||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
/chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -6910,15 +6853,9 @@ packages:
|
|||
resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==}
|
||||
dev: false
|
||||
|
||||
/core-js@3.29.1:
|
||||
resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/core-js@3.33.2:
|
||||
resolution: {integrity: sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/core-util-is@1.0.2:
|
||||
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
||||
|
@ -7757,20 +7694,6 @@ packages:
|
|||
es6-symbol: 3.1.3
|
||||
dev: false
|
||||
|
||||
/es6-promise@4.2.8:
|
||||
resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/es6-promisify@5.0.0:
|
||||
resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
es6-promise: 4.2.8
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/es6-symbol@3.1.3:
|
||||
resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==}
|
||||
dependencies:
|
||||
|
@ -9364,14 +9287,6 @@ packages:
|
|||
universalify: 0.1.2
|
||||
dev: false
|
||||
|
||||
/fs-minipass@1.2.7:
|
||||
resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
minipass: 2.9.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/fs-minipass@2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -9734,12 +9649,6 @@ packages:
|
|||
sparkles: 1.0.1
|
||||
dev: false
|
||||
|
||||
/google-protobuf@3.21.2:
|
||||
resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/gopd@1.0.1:
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||
dependencies:
|
||||
|
@ -10166,18 +10075,6 @@ packages:
|
|||
engines: {node: '>=16'}
|
||||
dev: false
|
||||
|
||||
/https-proxy-agent@2.2.4:
|
||||
resolution: {integrity: sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==}
|
||||
engines: {node: '>= 4.5.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
agent-base: 4.3.0
|
||||
debug: 3.2.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -12563,15 +12460,6 @@ packages:
|
|||
minipass: 3.3.6
|
||||
dev: false
|
||||
|
||||
/minipass@2.9.0:
|
||||
resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
yallist: 3.1.1
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -12590,14 +12478,6 @@ packages:
|
|||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: false
|
||||
|
||||
/minizlib@1.3.3:
|
||||
resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
minipass: 2.9.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -13015,13 +12895,13 @@ packages:
|
|||
set-blocking: 2.0.0
|
||||
dev: false
|
||||
|
||||
/nsfwjs@2.4.2(@tensorflow/tfjs@4.16.0):
|
||||
/nsfwjs@2.4.2(@tensorflow/tfjs@3.21.0):
|
||||
resolution: {integrity: sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==}
|
||||
peerDependencies:
|
||||
'@tensorflow/tfjs': ^3.18.0
|
||||
dependencies:
|
||||
'@nsfw-filter/gif-frames': 1.0.2
|
||||
'@tensorflow/tfjs': 4.16.0(seedrandom@3.0.5)
|
||||
'@tensorflow/tfjs': 3.21.0(seedrandom@3.0.5)
|
||||
dev: false
|
||||
|
||||
/nth-check@1.0.2:
|
||||
|
@ -14223,13 +14103,6 @@ packages:
|
|||
/process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
/progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/promise-limit@2.7.0:
|
||||
resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
|
||||
dev: false
|
||||
|
@ -14944,15 +14817,6 @@ packages:
|
|||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/rimraf@2.7.1:
|
||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
|
@ -15920,21 +15784,6 @@ packages:
|
|||
streamx: 2.15.5
|
||||
dev: false
|
||||
|
||||
/tar@4.4.19:
|
||||
resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==}
|
||||
engines: {node: '>=4.5'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
fs-minipass: 1.2.7
|
||||
minipass: 2.9.0
|
||||
minizlib: 1.3.3
|
||||
mkdirp: 0.5.6
|
||||
safe-buffer: 5.2.1
|
||||
yallist: 3.1.1
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/tar@6.2.0:
|
||||
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
Loading…
Reference in a new issue