Compare commits

...

19 Commits

Author SHA1 Message Date
syuilo 9fb0f7357a wip 2025-05-28 17:47:17 +09:00
syuilo bd8d0d78bf wip 2025-05-28 17:19:19 +09:00
syuilo 31c4237748 wip 2025-05-28 16:43:15 +09:00
syuilo fad3aed79e wip 2025-05-28 13:59:08 +09:00
syuilo 26819689bd Update ImageEffector.ts 2025-05-28 13:26:09 +09:00
syuilo a8cbbdff63 Update ImageEffector.ts 2025-05-28 12:57:28 +09:00
syuilo 09eb631fdc wip 2025-05-28 12:54:48 +09:00
syuilo 33486ebdf2 Update MkUploaderDialog.vue 2025-05-28 12:40:42 +09:00
syuilo 092048e2e9 Update watermarker.ts 2025-05-28 12:32:31 +09:00
syuilo 0a4ca368d4 Merge branch 'develop' into watermark 2025-05-28 11:47:24 +09:00
syuilo eea0fb2636 wip 2025-05-28 11:47:09 +09:00
おさむのひと 9bbc2028ad
feat: URLプレビューのリダイレクトを受け入れるかどうかを設定できるようにする (#16112)
* feat: URLプレビューのリダイレクトを受け入れるかどうかを設定できるようにする

* fix CHANGELOG.md

* fix lang
2025-05-27 20:46:22 +09:00
zyoshoka 97e916c912
refactor(frontend): revoke weakening endpoint param type of API caller for type safety (#16100) 2025-05-27 20:45:05 +09:00
anatawa12 e954060f3b
chore(dev): update vite configuration (#16110) 2025-05-27 15:18:37 +09:00
anatawa12 e078cd9296
fix: jest.js exits with zero value even if underlying jest exited with non-zero value (#16111) 2025-05-27 15:07:47 +09:00
かっこかり 1276e65049
fix(backend): follow-up of #16105 (#16109) 2025-05-27 14:42:48 +09:00
かっこかり 07c2de3749
fix(backend): typeormのbreaking changeに対応 (#16105)
* fix(backend): typeormのbreaking changeに対応

* attempt to fix test
2025-05-27 13:50:45 +09:00
renovate[bot] 47f4f11e3e
fix(deps): update [backend] update dependencies (#16087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 13:18:59 +09:00
zyoshoka d27075c5f5
fix(backend): correct invalid schema format specifying only `required` for `anyOf` (#16089)
* fix(backend): correct invalid schema format specifying only `required` for `anyOf`

* refactor(backend): make types derived from `allOf` or `anyOf` more strong
2025-05-27 08:57:09 +09:00
55 changed files with 1725 additions and 742 deletions

View File

@ -16,6 +16,7 @@
- デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。 - デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。
- 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。 - 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。
- したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。 - したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。
- Feat: プレビュー先がリダイレクトを伴う場合、リダイレクト先のコンテンツを取得しに行くか否かを設定できるように(#16043)
- Enhance: UIのアイコンデータの読み込みを軽量化 - Enhance: UIのアイコンデータの読み込みを軽量化
### Client ### Client

View File

@ -581,27 +581,6 @@ pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
- 生成後、ファイルをmigration下に移してください - 生成後、ファイルをmigration下に移してください
- 作成されたスクリプトは不必要な変更を含むため除去してください - 作成されたスクリプトは不必要な変更を含むため除去してください
### JSON SchemaのobjectでanyOfを使うとき
JSON Schemaで、objectに対してanyOfを使う場合、anyOfの中でpropertiesを定義しないこと。
バリデーションが効かないため。SchemaTypeもそのように作られており、objectのanyOf内のpropertiesは捨てられます
https://github.com/misskey-dev/misskey/pull/10082
テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合:
```ts
export const paramDef = {
type: 'object',
properties: {
hoge: { type: 'string', minLength: 1 },
fuga: { type: 'string', minLength: 1 },
},
anyOf: [
{ required: ['hoge'] },
{ required: ['fuga'] },
],
} as const;
```
### コネクションには`markRaw`せよ ### コネクションには`markRaw`せよ
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。

26
locales/index.d.ts vendored
View File

@ -5473,6 +5473,10 @@ export interface Locale extends ILocale {
* *
*/ */
"hideAllTips": string; "hideAllTips": string;
/**
*
*/
"defaultImageCompressionLevel": string;
"_chat": { "_chat": {
/** /**
* *
@ -11232,6 +11236,14 @@ export interface Locale extends ILocale {
* URLプレビューを有効にする * URLプレビューを有効にする
*/ */
"enable": string; "enable": string;
/**
*
*/
"allowRedirect": string;
/**
* URLがリダイレクトされる場合に
*/
"allowRedirectDescription": string;
/** /**
* (ms) * (ms)
*/ */
@ -12008,6 +12020,10 @@ export interface Locale extends ILocale {
* *
*/ */
"watermark": string; "watermark": string;
/**
*
*/
"defaultPreset": string;
"_watermarkEditor": { "_watermarkEditor": {
/** /**
* *
@ -12017,6 +12033,10 @@ export interface Locale extends ILocale {
* *
*/ */
"title": string; "title": string;
/**
*
*/
"cover": string;
/** /**
* *
*/ */
@ -12046,6 +12066,12 @@ export interface Locale extends ILocale {
*/ */
"image": string; "image": string;
}; };
"_imageEffector": {
/**
*
*/
"title": string;
};
} }
declare const locales: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View File

@ -1363,6 +1363,7 @@ abort: "中止"
tip: "ヒントとコツ" tip: "ヒントとコツ"
redisplayAllTips: "全ての「ヒントとコツ」を再表示" redisplayAllTips: "全ての「ヒントとコツ」を再表示"
hideAllTips: "全ての「ヒントとコツ」を非表示" hideAllTips: "全ての「ヒントとコツ」を非表示"
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
_chat: _chat:
noMessagesYet: "まだメッセージはありません" noMessagesYet: "まだメッセージはありません"
@ -2987,6 +2988,8 @@ _offlineScreen:
_urlPreviewSetting: _urlPreviewSetting:
title: "URLプレビューの設定" title: "URLプレビューの設定"
enable: "URLプレビューを有効にする" enable: "URLプレビューを有効にする"
allowRedirect: "プレビュー先のリダイレクトを許可"
allowRedirectDescription: "入力されたURLがリダイレクトされる場合に、そのリダイレクト先をたどってプレビューを表示するかどうかを設定します。無効にするとサーバーリソースの節約になりますが、リダイレクト先の内容は表示されなくなります。"
timeout: "プレビュー取得時のタイムアウト(ms)" timeout: "プレビュー取得時のタイムアウト(ms)"
timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。" timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
maximumContentLength: "Content-Lengthの最大値(byte)" maximumContentLength: "Content-Lengthの最大値(byte)"
@ -3216,9 +3219,11 @@ _userLists:
tip: "任意のユーザーが含まれるリストを作成できます。作成したリストはタイムラインとして表示可能です。" tip: "任意のユーザーが含まれるリストを作成できます。作成したリストはタイムラインとして表示可能です。"
watermark: "ウォーターマーク" watermark: "ウォーターマーク"
defaultPreset: "デフォルトのプリセット"
_watermarkEditor: _watermarkEditor:
tip: "画像にクレジット情報などのウォーターマークを追加することができます。" tip: "画像にクレジット情報などのウォーターマークを追加することができます。"
title: "ウォーターマークの編集" title: "ウォーターマークの編集"
cover: "全体に被せる"
repeat: "敷き詰める" repeat: "敷き詰める"
opacity: "不透明度" opacity: "不透明度"
scale: "サイズ" scale: "サイズ"
@ -3226,3 +3231,6 @@ _watermarkEditor:
position: "位置" position: "位置"
type: "タイプ" type: "タイプ"
image: "画像" image: "画像"
_imageEffector:
title: "エフェクト"

View File

@ -17,4 +17,15 @@ args.push(...[
...process.argv.slice(2), ...process.argv.slice(2),
]); ]);
child_process.spawn(process.execPath, args, { stdio: 'inherit' }); const child = child_process.spawn(process.execPath, args, { stdio: 'inherit' });
child.on('error', (err) => {
console.error('Failed to start Jest:', err);
process.exit(1);
});
child.on('exit', (code, signal) => {
if (code === null) {
process.exit(128 + signal);
} else {
process.exit(code);
}
});

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddUrlPreviewAllowRedirect1748310233000 {
name = 'AddUrlPreviewAllowRedirect1748310233000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "urlPreviewAllowRedirect" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "urlPreviewAllowRedirect"`);
}
}

View File

@ -67,8 +67,8 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.815.0", "@aws-sdk/client-s3": "3.817.0",
"@aws-sdk/lib-storage": "3.815.0", "@aws-sdk/lib-storage": "3.817.0",
"@discordapp/twemoji": "15.1.0", "@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "5.0.2", "@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.2", "@fastify/cookie": "11.0.2",
@ -81,9 +81,9 @@
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.1", "@misskey-dev/summaly": "5.2.1",
"@napi-rs/canvas": "0.1.70", "@napi-rs/canvas": "0.1.70",
"@nestjs/common": "11.1.1", "@nestjs/common": "11.1.2",
"@nestjs/core": "11.1.1", "@nestjs/core": "11.1.2",
"@nestjs/testing": "11.1.1", "@nestjs/testing": "11.1.2",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "8.55.0", "@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.55.0", "@sentry/profiling-node": "8.55.0",
@ -159,7 +159,7 @@
"qrcode": "1.5.4", "qrcode": "1.5.4",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.21.5", "re2": "1.22.1",
"redis-info": "3.1.0", "redis-info": "3.1.0",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
@ -173,7 +173,7 @@
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.26.1", "systeminformation": "5.27.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.3", "tmp": "0.2.3",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
@ -188,7 +188,7 @@
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.7.0", "@jest/globals": "29.7.0",
"@nestjs/platform-express": "10.4.17", "@nestjs/platform-express": "10.4.18",
"@sentry/vue": "9.22.0", "@sentry/vue": "9.22.0",
"@simplewebauthn/types": "12.0.0", "@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.38", "@swc/jest": "0.2.38",

View File

@ -218,7 +218,17 @@ type NullOrUndefined<p extends Schema, T> =
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union // Get intersection from union
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type PartialIntersection<T> = Partial<UnionToIntersection<T>>;
type ArrayToIntersection<T extends ReadonlyArray<Schema>> =
T extends readonly [infer Head, ...infer Tail]
? Head extends Schema
? Tail extends ReadonlyArray<Schema>
? Tail extends []
? SchemaType<Head>
: SchemaType<Head> & ArrayToIntersection<Tail>
: never
: never
: never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// To get union, we use `Foo extends any ? Hoge<Foo> : never` // To get union, we use `Foo extends any ? Hoge<Foo> : never`
@ -236,8 +246,8 @@ type ObjectSchemaTypeDef<p extends Schema> =
: never : never
: ObjType<p['properties'], NonNullable<p['required']>> : ObjType<p['properties'], NonNullable<p['required']>>
: :
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> :
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? ArrayToIntersection<p['allOf']> :
p['additionalProperties'] extends true ? Record<string, any> : p['additionalProperties'] extends true ? Record<string, any> :
p['additionalProperties'] extends Schema ? p['additionalProperties'] extends Schema ?
p['additionalProperties'] extends infer AdditionalProperties ? p['additionalProperties'] extends infer AdditionalProperties ?
@ -277,7 +287,8 @@ export type SchemaTypeDef<p extends Schema> =
p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] : p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
any[] any[]
) : ) :
p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> : p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> :
p['allOf'] extends ReadonlyArray<Schema> ? ArrayToIntersection<p['allOf']> :
p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> : p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
any; any;

View File

@ -619,6 +619,11 @@ export class MiMeta {
}) })
public urlPreviewEnabled: boolean; public urlPreviewEnabled: boolean;
@Column('boolean', {
default: true,
})
public urlPreviewAllowRedirect: boolean;
@Column('integer', { @Column('integer', {
default: 10000, default: 10000,
}) })

View File

@ -162,14 +162,21 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object',
properties: {
fileId: { type: 'string', format: 'misskey:id' },
url: { type: 'string' },
},
anyOf: [ anyOf: [
{ required: ['fileId'] }, {
{ required: ['url'] }, type: 'object',
properties: {
fileId: { type: 'string', format: 'misskey:id' },
},
required: ['fileId'],
},
{
type: 'object',
properties: {
url: { type: 'string' },
},
required: ['url'],
},
], ],
} as const; } as const;
@ -186,15 +193,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService, private idService: IdService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ const file = await this.driveFilesRepository.findOneBy(
where: [{ 'fileId' in ps
url: ps.url, ? { id: ps.fileId }
}, { : [{ url: ps.url }, { thumbnailUrl: ps.url }, { webpublicUrl: ps.url }],
thumbnailUrl: ps.url, );
}, {
webpublicUrl: ps.url,
}],
});
if (file == null) { if (file == null) {
throw new ApiError(meta.errors.noSuchFile); throw new ApiError(meta.errors.noSuchFile);

View File

@ -37,29 +37,45 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
id: { type: 'string', format: 'misskey:id' }, anyOf: [
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, {
fileId: { type: 'string', format: 'misskey:id' }, type: 'object',
category: { properties: {
type: 'string', id: { type: 'string', format: 'misskey:id' },
nullable: true, },
description: 'Use `null` to reset the category.', required: ['id'],
},
{
type: 'object',
properties: {
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
},
required: ['name'],
},
],
},
{
type: 'object',
properties: {
fileId: { type: 'string', format: 'misskey:id' },
category: {
type: 'string',
nullable: true,
description: 'Use `null` to reset the category.',
},
aliases: { type: 'array', items: {
type: 'string',
} },
license: { type: 'string', nullable: true },
isSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
},
}, },
aliases: { type: 'array', items: {
type: 'string',
} },
license: { type: 'string', nullable: true },
isSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
},
anyOf: [
{ required: ['id'] },
{ required: ['name'] },
], ],
} as const; } as const;
@ -78,10 +94,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
} }
// JSON schemeのanyOfの型変換がうまくいっていないらしい const required = 'id' in ps
const required = { id: ps.id, name: ps.name } as ? { id: ps.id, name: 'name' in ps ? ps.name as string : undefined }
| { id: MiEmoji['id']; name?: string } : { name: ps.name };
| { id?: MiEmoji['id']; name: string };
const error = await this.customEmojiService.update({ const error = await this.customEmojiService.update({
...required, ...required,

View File

@ -495,6 +495,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
urlPreviewAllowRedirect: {
type: 'boolean',
optional: false, nullable: false,
},
urlPreviewTimeout: { urlPreviewTimeout: {
type: 'number', type: 'number',
optional: false, nullable: false, optional: false, nullable: false,
@ -704,6 +708,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notesPerOneAd: instance.notesPerOneAd, notesPerOneAd: instance.notesPerOneAd,
summalyProxy: instance.urlPreviewSummaryProxyUrl, summalyProxy: instance.urlPreviewSummaryProxyUrl,
urlPreviewEnabled: instance.urlPreviewEnabled, urlPreviewEnabled: instance.urlPreviewEnabled,
urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect,
urlPreviewTimeout: instance.urlPreviewTimeout, urlPreviewTimeout: instance.urlPreviewTimeout,
urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,

View File

@ -170,6 +170,7 @@ export const paramDef = {
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
}, },
urlPreviewEnabled: { type: 'boolean' }, urlPreviewEnabled: { type: 'boolean' },
urlPreviewAllowRedirect: { type: 'boolean' },
urlPreviewTimeout: { type: 'integer' }, urlPreviewTimeout: { type: 'integer' },
urlPreviewMaximumContentLength: { type: 'integer' }, urlPreviewMaximumContentLength: { type: 'integer' },
urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewRequireContentLength: { type: 'boolean' },
@ -664,6 +665,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.urlPreviewEnabled = ps.urlPreviewEnabled; set.urlPreviewEnabled = ps.urlPreviewEnabled;
} }
if (ps.urlPreviewAllowRedirect !== undefined) {
set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect;
}
if (ps.urlPreviewTimeout !== undefined) { if (ps.urlPreviewTimeout !== undefined) {
set.urlPreviewTimeout = ps.urlPreviewTimeout; set.urlPreviewTimeout = ps.urlPreviewTimeout;
} }

View File

@ -43,14 +43,21 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object',
properties: {
fileId: { type: 'string', format: 'misskey:id' },
url: { type: 'string' },
},
anyOf: [ anyOf: [
{ required: ['fileId'] }, {
{ required: ['url'] }, type: 'object',
properties: {
fileId: { type: 'string', format: 'misskey:id' },
},
required: ['fileId'],
},
{
type: 'object',
properties: {
url: { type: 'string' },
},
required: ['url'],
},
], ],
} as const; } as const;
@ -64,21 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService, private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
let file: MiDriveFile | null = null; const file = await this.driveFilesRepository.findOneBy(
'fileId' in ps
if (ps.fileId) { ? { id: ps.fileId }
file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); : [{ url: ps.url }, { webpublicUrl: ps.url }, { thumbnailUrl: ps.url }],
} else if (ps.url) { );
file = await this.driveFilesRepository.findOne({
where: [{
url: ps.url,
}, {
webpublicUrl: ps.url,
}, {
thumbnailUrl: ps.url,
}],
});
}
if (file == null) { if (file == null) {
throw new ApiError(meta.errors.noSuchFile); throw new ApiError(meta.errors.noSuchFile);

View File

@ -15,14 +15,21 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object',
properties: {
tokenId: { type: 'string', format: 'misskey:id' },
token: { type: 'string', nullable: true },
},
anyOf: [ anyOf: [
{ required: ['tokenId'] }, {
{ required: ['token'] }, type: 'object',
properties: {
tokenId: { type: 'string', format: 'misskey:id' },
},
required: ['tokenId'],
},
{
type: 'object',
properties: {
token: { type: 'string', nullable: true },
},
required: ['token'],
},
], ],
} as const; } as const;
@ -33,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository, private accessTokensRepository: AccessTokensRepository,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
if (ps.tokenId) { if ('tokenId' in ps) {
const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } }); const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
if (tokenExist) { if (tokenExist) {

View File

@ -28,38 +28,53 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
reply: { type: 'boolean', nullable: true, default: null }, anyOf: [
renote: { type: 'boolean', nullable: true, default: null }, {
withFiles: { type: 'object',
type: 'boolean', properties: {
default: false, tag: { type: 'string', minLength: 1 },
description: 'Only show notes that have attached files.', },
}, required: ['tag'],
poll: { type: 'boolean', nullable: true, default: null },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
tag: { type: 'string', minLength: 1 },
query: {
type: 'array',
description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
items: {
type: 'array',
items: {
type: 'string',
minLength: 1,
}, },
minItems: 1, {
}, type: 'object',
minItems: 1, properties: {
query: {
type: 'array',
description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
items: {
type: 'array',
items: {
type: 'string',
minLength: 1,
},
minItems: 1,
},
minItems: 1,
},
},
required: ['query'],
},
],
},
{
type: 'object',
properties: {
reply: { type: 'boolean', nullable: true, default: null },
renote: { type: 'boolean', nullable: true, default: null },
withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
poll: { type: 'boolean', nullable: true, default: null },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
}, },
},
anyOf: [
{ required: ['tag'] },
{ required: ['query'] },
], ],
} as const; } as const;
@ -87,12 +102,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
try { try {
if (ps.tag) { if ('tag' in ps) {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection'); if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] }); query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
} else { } else {
query.andWhere(new Brackets(qb => { query.andWhere(new Brackets(qb => {
for (const tags of ps.query!) { for (const tags of ps.query) {
qb.orWhere(new Brackets(qb => { qb.orWhere(new Brackets(qb => {
for (const tag of tags) { for (const tag of tags) {
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection'); if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');

View File

@ -33,15 +33,22 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object',
properties: {
pageId: { type: 'string', format: 'misskey:id' },
name: { type: 'string' },
username: { type: 'string' },
},
anyOf: [ anyOf: [
{ required: ['pageId'] }, {
{ required: ['name', 'username'] }, type: 'object',
properties: {
pageId: { type: 'string', format: 'misskey:id' },
},
required: ['pageId'],
},
{
type: 'object',
properties: {
name: { type: 'string' },
username: { type: 'string' },
},
required: ['name', 'username'],
},
], ],
} as const; } as const;
@ -59,9 +66,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
let page: MiPage | null = null; let page: MiPage | null = null;
if (ps.pageId) { if ('pageId' in ps) {
page = await this.pagesRepository.findOneBy({ id: ps.pageId }); page = await this.pagesRepository.findOneBy({ id: ps.pageId });
} else if (ps.name && ps.username) { } else {
const author = await this.usersRepository.findOneBy({ const author = await this.usersRepository.findOneBy({
host: IsNull(), host: IsNull(),
usernameLower: ps.username.toLowerCase(), usernameLower: ps.username.toLowerCase(),

View File

@ -47,23 +47,38 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
sinceId: { type: 'string', format: 'misskey:id' }, anyOf: [
untilId: { type: 'string', format: 'misskey:id' }, {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
username: { type: 'string' }, },
host: { required: ['userId'],
type: 'string', },
nullable: true, {
description: 'The local host is represented with `null`.', type: 'object',
properties: {
username: { type: 'string' },
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
required: ['username', 'host'],
},
],
},
{
type: 'object',
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
}, },
},
anyOf: [
{ required: ['userId'] },
{ required: ['username', 'host'] },
], ],
} as const; } as const;
@ -85,9 +100,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService, private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy(ps.userId != null const user = await this.usersRepository.findOneBy('userId' in ps
? { id: ps.userId } ? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
if (user == null) { if (user == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);

View File

@ -54,25 +54,39 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
sinceId: { type: 'string', format: 'misskey:id' }, anyOf: [
untilId: { type: 'string', format: 'misskey:id' }, {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
username: { type: 'string' }, },
host: { required: ['userId'],
type: 'string', },
nullable: true, {
description: 'The local host is represented with `null`.', type: 'object',
properties: {
username: { type: 'string' },
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
required: ['username', 'host'],
},
],
},
{
type: 'object',
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
birthday: { ...birthdaySchema, nullable: true },
},
}, },
birthday: { ...birthdaySchema, nullable: true },
},
anyOf: [
{ required: ['userId'] },
{ required: ['username', 'host'] },
], ],
} as const; } as const;
@ -94,9 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService, private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy(ps.userId != null const user = await this.usersRepository.findOneBy('userId' in ps
? { id: ps.userId } ? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
if (user == null) { if (user == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);

View File

@ -114,7 +114,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { userId: {
anyOf: [ oneOf: [
{ type: 'string', format: 'misskey:id' }, { type: 'string', format: 'misskey:id' },
{ {
type: 'array', type: 'array',

View File

@ -26,17 +26,32 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, anyOf: [
detail: { type: 'boolean', default: true }, {
type: 'object',
username: { type: 'string', nullable: true }, properties: {
host: { type: 'string', nullable: true }, username: { type: 'string', nullable: true },
}, },
anyOf: [ required: ['username'],
{ required: ['username'] }, },
{ required: ['host'] }, {
type: 'object',
properties: {
host: { type: 'string', nullable: true },
},
required: ['host'],
},
],
},
{
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
detail: { type: 'boolean', default: true },
},
},
], ],
} as const; } as const;
@ -47,8 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) { ) {
super(meta, paramDef, (ps, me) => { super(meta, paramDef, (ps, me) => {
return this.userSearchService.searchByUsernameAndHost({ return this.userSearchService.searchByUsernameAndHost({
username: ps.username, username: 'username' in ps ? ps.username : undefined,
host: ps.host, host: 'host' in ps ? ps.host : undefined,
}, { }, {
limit: ps.limit, limit: ps.limit,
detail: ps.detail, detail: ps.detail,

View File

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
process.env.NODE_ENV = 'test';
import { getValidator } from '../../../../../test/prelude/get-api-validator.js';
import { paramDef } from './show.js';
const VALID = true;
const INVALID = false;
describe('api:users/show', () => {
describe('validation', () => {
const v = getValidator(paramDef);
test('Reject empty', () => expect(v({})).toBe(INVALID));
test('Reject host only', () => expect(v({ host: 'misskey.test' })).toBe(INVALID));
test('Accept userId only', () => expect(v({ userId: '1' })).toBe(VALID));
test('Accept username and host', () => expect(v({ username: 'alice', host: 'misskey.test' })).toBe(VALID));
});
});

View File

@ -59,23 +59,44 @@ export const meta = {
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', allOf: [
properties: { {
userId: { type: 'string', format: 'misskey:id' }, anyOf: [
userIds: { type: 'array', uniqueItems: true, items: { {
type: 'string', format: 'misskey:id', type: 'object',
} }, properties: {
username: { type: 'string' }, userId: { type: 'string', format: 'misskey:id' },
host: { },
type: 'string', required: ['userId'],
nullable: true, },
description: 'The local host is represented with `null`.', {
type: 'object',
properties: {
userIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id',
} },
},
required: ['userIds'],
},
{
type: 'object',
properties: {
username: { type: 'string' },
},
required: ['username'],
},
],
},
{
type: 'object',
properties: {
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
}, },
},
anyOf: [
{ required: ['userId'] },
{ required: ['userIds'] },
{ required: ['username'] },
], ],
} as const; } as const;
@ -102,9 +123,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let user; let user;
const isModerator = await this.roleService.isModerator(me); const isModerator = await this.roleService.isModerator(me);
ps.username = ps.username?.trim(); if ('username' in ps) {
ps.username = ps.username.trim();
}
if (ps.userIds) { if ('userIds' in ps) {
if (ps.userIds.length === 0) { if (ps.userIds.length === 0) {
return []; return [];
} }
@ -129,7 +152,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return _users.map(u => _userMap.get(u.id)!); return _users.map(u => _userMap.get(u.id)!);
} else { } else {
// Lookup user // Lookup user
if (typeof ps.host === 'string' && typeof ps.username === 'string') { if (typeof ps.host === 'string' && 'username' in ps) {
if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) { if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);
} }
@ -139,7 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.failedToResolveRemoteUser); throw new ApiError(meta.errors.failedToResolveRemoteUser);
}); });
} else { } else {
const q: FindOptionsWhere<MiUser> = ps.userId != null const q: FindOptionsWhere<MiUser> = 'userId' in ps
? { id: ps.userId } ? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; : { usernameLower: ps.username!.toLowerCase(), host: IsNull() };

View File

@ -89,7 +89,8 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
schema.required = undefined; schema.required = undefined;
} }
const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1); const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1)
|| ['allOf', 'oneOf', 'anyOf'].some(o => (Array.isArray(schema[o]) && schema[o].length >= 0));
const info = { const info = {
operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない

View File

@ -122,7 +122,7 @@ export class UrlPreviewService {
: undefined; : undefined;
return summaly(url, { return summaly(url, {
followRedirects: false, followRedirects: this.meta.urlPreviewAllowRedirect,
lang: lang ?? 'ja-JP', lang: lang ?? 'ja-JP',
agent: agent, agent: agent,
userAgent: meta.urlPreviewUserAgent ?? undefined, userAgent: meta.urlPreviewUserAgent ?? undefined,
@ -137,6 +137,7 @@ export class UrlPreviewService {
const queryStr = query({ const queryStr = query({
url: url, url: url,
lang: lang ?? 'ja-JP', lang: lang ?? 'ja-JP',
followRedirects: this.meta.urlPreviewAllowRedirect,
userAgent: meta.urlPreviewUserAgent ?? undefined, userAgent: meta.urlPreviewUserAgent ?? undefined,
operationTimeout: meta.urlPreviewTimeout, operationTimeout: meta.urlPreviewTimeout,
contentLengthLimit: meta.urlPreviewMaximumContentLength, contentLengthLimit: meta.urlPreviewMaximumContentLength,

View File

@ -162,10 +162,10 @@ describe('AbuseReportNotificationService', () => {
emailService.sendEmail.mockClear(); emailService.sendEmail.mockClear();
webhookService.enqueueSystemWebhook.mockClear(); webhookService.enqueueSystemWebhook.mockClear();
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await userProfilesRepository.delete({}); await userProfilesRepository.createQueryBuilder().delete().execute();
await systemWebhooksRepository.delete({}); await systemWebhooksRepository.createQueryBuilder().delete().execute();
await abuseReportNotificationRecipientRepository.delete({}); await abuseReportNotificationRecipientRepository.createQueryBuilder().delete().execute();
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -103,10 +103,10 @@ describe('AnnouncementService', () => {
afterEach(async () => { afterEach(async () => {
await Promise.all([ await Promise.all([
app.get(DI.metasRepository).delete({}), app.get(DI.metasRepository).createQueryBuilder().delete().execute(),
usersRepository.delete({}), usersRepository.createQueryBuilder().delete().execute(),
announcementsRepository.delete({}), announcementsRepository.createQueryBuilder().delete().execute(),
announcementReadsRepository.delete({}), announcementReadsRepository.createQueryBuilder().delete().execute(),
]); ]);
await app.close(); await app.close();

View File

@ -86,7 +86,7 @@ describe('CustomEmojiService', () => {
} }
afterEach(async () => { afterEach(async () => {
await emojisRepository.delete({}); await emojisRepository.createQueryBuilder().delete().execute();
}); });
describe('単独', () => { describe('単独', () => {

View File

@ -85,9 +85,9 @@ describe('FlashService', () => {
}); });
afterEach(async () => { afterEach(async () => {
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await userProfilesRepository.delete({}); await userProfilesRepository.createQueryBuilder().delete().execute();
await flashsRepository.delete({}); await flashsRepository.createQueryBuilder().delete().execute();
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -159,10 +159,10 @@ describe('RoleService', () => {
clock.uninstall(); clock.uninstall();
await Promise.all([ await Promise.all([
app.get(DI.metasRepository).delete({}), app.get(DI.metasRepository).createQueryBuilder().delete().execute(),
usersRepository.delete({}), usersRepository.createQueryBuilder().delete().execute(),
rolesRepository.delete({}), rolesRepository.createQueryBuilder().delete().execute(),
roleAssignmentsRepository.delete({}), roleAssignmentsRepository.createQueryBuilder().delete().execute(),
]); ]);
await app.close(); await app.close();

View File

@ -101,8 +101,8 @@ describe('SystemWebhookService', () => {
} }
async function afterEachImpl() { async function afterEachImpl() {
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await systemWebhooksRepository.delete({}); await systemWebhooksRepository.createQueryBuilder().delete().execute();
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

View File

@ -127,7 +127,7 @@ describe('UserSearchService', () => {
}); });
afterEach(async () => { afterEach(async () => {
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -95,8 +95,8 @@ describe('UserWebhookService', () => {
} }
async function afterEachImpl() { async function afterEachImpl() {
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await userWebhooksRepository.delete({}); await userWebhooksRepository.createQueryBuilder().delete().execute();
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

View File

@ -111,8 +111,8 @@ describe('WebhookTestService', () => {
userWebhookService.fetchWebhooks.mockClear(); userWebhookService.fetchWebhooks.mockClear();
systemWebhookService.fetchSystemWebhooks.mockClear(); systemWebhookService.fetchSystemWebhooks.mockClear();
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await userProfilesRepository.delete({}); await userProfilesRepository.createQueryBuilder().delete().execute();
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -157,8 +157,8 @@ describe('CheckModeratorsActivityProcessorService', () => {
afterEach(async () => { afterEach(async () => {
clock.uninstall(); clock.uninstall();
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
await userProfilesRepository.delete({}); await userProfilesRepository.createQueryBuilder().delete().execute();
roleService.getModerators.mockReset(); roleService.getModerators.mockReset();
announcementService.create.mockReset(); announcementService.create.mockReset();
emailService.sendEmail.mockReset(); emailService.sendEmail.mockReset();

View File

@ -41,7 +41,7 @@ describe('/drive/files/create', () => {
idService = module.get(IdService); idService = module.get(IdService);
const usersRepository = module.get<UsersRepository>(DI.usersRepository); const usersRepository = module.get<UsersRepository>(DI.usersRepository);
await usersRepository.delete({}); await usersRepository.createQueryBuilder().delete().execute();
root = await usersRepository.insert({ root = await usersRepository.insert({
id: idService.gen(), id: idService.gen(),
username: 'root', username: 'root',
@ -50,7 +50,7 @@ describe('/drive/files/create', () => {
}).then(x => usersRepository.findOneByOrFail(x.identifiers[0])); }).then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
const userProfilesRepository = module.get<UserProfilesRepository>(DI.userProfilesRepository); const userProfilesRepository = module.get<UserProfilesRepository>(DI.userProfilesRepository);
await userProfilesRepository.delete({}); await userProfilesRepository.createQueryBuilder().delete().execute();
await userProfilesRepository.insert({ await userProfilesRepository.insert({
userId: root.id, userId: root.id,
}); });

View File

@ -66,9 +66,15 @@ export function getConfig(): UserConfig {
return { return {
base: '/embed_vite/', base: '/embed_vite/',
// The console is shared with backend, so clearing the console will also clear the backend log.
clearScreen: false,
server: { server: {
host, // The backend allows access from any addresses, so vite also allows access from any addresses.
host: '0.0.0.0',
allowedHosts: host ? [host] : undefined,
port: 5174, port: 5174,
strictPort: true,
hmr: { hmr: {
// バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される
// そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない

View File

@ -0,0 +1,64 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkFolder :defaultOpen="true">
<template #label>{{ fx.id }}</template>
<template #footer>
<MkButton @click="emit('del')">{{ i18n.ts.remove }}</MkButton>
</template>
<div :class="$style.root" class="_gaps">
<div v-for="[k, v] in Object.entries(fx.params)" :key="k">
<MkSwitch v-if="v.type === 'boolean'" v-model="layer.params[k]">
<template #label>{{ k }}</template>
</MkSwitch>
<MkRange v-else-if="v.type === 'number'" v-model="layer.params[k]" continuousUpdate :min="v.min" :max="v.max" :step="v.step">
<template #label>{{ k }}</template>
</MkRange>
<div v-else-if="v.type === 'seed'">
<MkRange v-model="layer.params[k]" continuousUpdate type="number" :min="0" :max="10000" :step="1">
<template #label>{{ k }}</template>
</MkRange>
</div>
</div>
</div>
</MkFolder>
</template>
<script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
import { v4 as uuid } from 'uuid';
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
import { i18n } from '@/i18n.js';
import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
import FormSlot from '@/components/form/slot.vue';
import MkPositionSelector from '@/components/MkPositionSelector.vue';
import * as os from '@/os.js';
import { selectFile } from '@/utility/drive.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js';
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
const fx = FXS.find((fx) => fx.id === layer.value.fxId);
if (fx == null) {
throw new Error(`Unrecognized effect: ${layer.value.fxId}`);
}
const emit = defineEmits<{
(e: 'del'): void;
}>();
</script>
<style module>
.root {
}
</style>

View File

@ -0,0 +1,217 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModalWindow
ref="dialog"
:width="1000"
:height="600"
:scroll="false"
:withOkButton="true"
@close="cancel()"
@ok="save()"
@closed="emit('closed')"
>
<template #header><i class="ti ti-sparkles"></i> {{ i18n.ts._imageEffector.title }}</template>
<div :class="$style.root">
<div :class="$style.container">
<div :class="$style.preview">
<canvas ref="canvasEl" :class="$style.previewCanvas"></canvas>
<div :class="$style.previewContainer">
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
<div class="_acrylic" :class="$style.previewControls">
</div>
</div>
</div>
<div :class="$style.controls" class="_gaps">
<XLayer
v-for="(layer, i) in layers"
:key="layer.id"
v-model:layer="layers[i]"
@del="onLayerDelete(layer)"
></XLayer>
<MkButton rounded primary @click="addEffect"><i class="ti ti-plus"></i></MkButton>
</div>
</div>
</div>
</MkModalWindow>
</template>
<script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
import { v4 as uuid } from 'uuid';
import type { WatermarkPreset } from '@/utility/watermark.js';
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
import { i18n } from '@/i18n.js';
import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import XLayer from '@/components/MkImageEffectorDialog.Layer.vue';
import * as os from '@/os.js';
import { deepClone } from '@/utility/clone.js';
const props = defineProps<{
image: HTMLImageElement;
}>();
const emit = defineEmits<{
(ev: 'ok', preset: WatermarkPreset): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
const dialog = useTemplateRef('dialog');
function cancel() {
emit('cancel');
dialog.value?.close();
}
const layers = reactive<ImageEffectorLayer[]>([]);
watch(layers, async () => {
if (renderer != null) {
renderer.updateLayers(layers);
}
}, { deep: true });
function addEffect(ev: MouseEvent) {
os.popupMenu(FXS.filter(fx => fx.id !== 'watermarkPlacement').map((fx) => ({
text: fx.id,
action: () => {
layers.push({
id: uuid(),
fxId: fx.id,
params: Object.fromEntries(Object.entries(fx.params).map(([k, v]) => [k, v.default])),
});
},
})), ev.currentTarget ?? ev.target);
}
function onLayerDelete(layer: ImageEffectorLayer) {
const index = layers.indexOf(layer);
if (index !== -1) {
layers.splice(index, 1);
}
}
const canvasEl = useTemplateRef('canvasEl');
let renderer: ImageEffector | null = null;
onMounted(async () => {
renderer = new ImageEffector({
canvas: canvasEl.value,
width: props.image.width,
height: props.image.height,
layers: layers,
originalImage: props.image,
});
await renderer!.bakeTextures();
renderer!.render();
});
onUnmounted(() => {
if (renderer != null) {
renderer.destroy();
renderer = null;
}
});
</script>
<style module>
.root {
container-type: inline-size;
height: 100%;
}
.container {
height: 100%;
display: grid;
grid-template-columns: 1fr 400px;
}
.preview {
position: relative;
background-color: var(--MI_THEME-bg);
background-size: auto auto;
background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
}
.previewContainer {
display: flex;
flex-direction: column;
height: 100%;
user-select: none;
-webkit-user-drag: none;
}
.previewTitle {
position: absolute;
z-index: 100;
top: 8px;
left: 8px;
padding: 6px 10px;
border-radius: 6px;
font-size: 85%;
}
.previewControls {
position: absolute;
z-index: 100;
bottom: 8px;
right: 8px;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 6px;
}
.previewControlsButton {
&.active {
color: var(--MI_THEME-accent);
}
}
.previewSpinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
user-select: none;
-webkit-user-drag: none;
}
.previewCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
object-fit: contain;
}
.controls {
padding: 24px;
overflow-y: scroll;
}
@container (max-width: 800px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
}
</style>

View File

@ -40,8 +40,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div><MkCondensedLine :minScale="2 / 3">{{ ctx.name }}</MkCondensedLine></div> <div><MkCondensedLine :minScale="2 / 3">{{ ctx.name }}</MkCondensedLine></div>
<div :class="$style.itemInfo"> <div :class="$style.itemInfo">
<span>{{ ctx.file.type }}</span> <span>{{ ctx.file.type }}</span>
<span>{{ bytes(ctx.file.size) }}</span>
<span v-if="ctx.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(ctx.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - ctx.compressedSize / ctx.file.size) * 100) }) }})</span> <span v-if="ctx.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(ctx.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - ctx.compressedSize / ctx.file.size) * 100) }) }})</span>
<span v-else>{{ bytes(ctx.file.size) }}</span>
</div> </div>
<div> <div>
</div> </div>
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, markRaw, onMounted, ref, triggerRef, useTemplateRef, watch } from 'vue'; import { computed, defineAsyncComponent, markRaw, onMounted, onUnmounted, ref, triggerRef, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer'; import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
@ -96,7 +96,8 @@ import { isWebpSupported } from '@/utility/isWebpSupported.js';
import { uploadFile, UploadAbortedError } from '@/utility/drive.js'; import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { ensureSignin } from '@/i.js'; import { ensureSignin } from '@/i.js';
import { Watermarker } from '@/utility/watermarker.js'; import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
import { makeImageEffectorLayers } from '@/utility/watermark.js';
const $i = ensureSignin(); const $i = ensureSignin();
@ -152,7 +153,7 @@ const items = ref<{
uploaded: Misskey.entities.DriveFile | null; uploaded: Misskey.entities.DriveFile | null;
uploadFailed: boolean; uploadFailed: boolean;
aborted: boolean; aborted: boolean;
compressionLevel: 0 | 1 | 2 | 3; compressionLevel: number;
compressedSize?: number | null; compressedSize?: number | null;
preprocessedFile?: Blob | null; preprocessedFile?: Blob | null;
file: File; file: File;
@ -288,6 +289,23 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
}); });
} }
if (IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) {
menu.push({
icon: 'ti ti-sparkles',
text: i18n.ts._imageEffector.title,
action: async () => {
const img = await getImageElement(item.file);
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkImageEffectorDialog.vue')), {
image: img,
}, {
ok: () => {
},
closed: () => dispose(),
});
},
});
}
if (WATERMARK_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) { if (WATERMARK_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) {
function changeWatermarkPreset(presetId: string | null) { function changeWatermarkPreset(presetId: string | null) {
item.watermarkPresetId = presetId; item.watermarkPresetId = presetId;
@ -362,6 +380,7 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
icon: 'ti ti-x', icon: 'ti ti-x',
text: i18n.ts.remove, text: i18n.ts.remove,
action: () => { action: () => {
URL.revokeObjectURL(item.thumbnail);
items.value.splice(items.value.indexOf(item), 1); items.value.splice(items.value.indexOf(item), 1);
}, },
}); });
@ -485,11 +504,11 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId); const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
if (needsWatermark && preset != null) { if (needsWatermark && preset != null) {
const canvas = window.document.createElement('canvas'); const canvas = window.document.createElement('canvas');
const renderer = new Watermarker({ const renderer = new ImageEffector({
canvas: canvas, canvas: canvas,
width: img.width, width: img.width,
height: img.height, height: img.height,
preset: preset, layers: makeImageEffectorLayers(preset.layers),
originalImage: img, originalImage: img,
}); });
@ -557,8 +576,8 @@ function initializeFile(file: File) {
aborted: false, aborted: false,
uploaded: null, uploaded: null,
uploadFailed: false, uploadFailed: false,
compressionLevel: 2 as 0 | 1 | 2 | 3, compressionLevel: prefer.s.defaultImageCompressionLevel,
watermarkPresetId: null, watermarkPresetId: prefer.s.defaultWatermarkPresetId,
file: markRaw(file), file: markRaw(file),
}; };
items.value.push(item); items.value.push(item);
@ -572,6 +591,12 @@ onMounted(() => {
initializeFile(file); initializeFile(file);
} }
}); });
onUnmounted(() => {
for (const item of items.value) {
URL.revokeObjectURL(item.thumbnail);
}
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -13,8 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSlot> <FormSlot>
<template #label>{{ i18n.ts._watermarkEditor.position }}</template> <template #label>{{ i18n.ts._watermarkEditor.position }}</template>
<MkPositionSelector <MkPositionSelector
v-model:x="layer.alignX" v-model:x="layer.align.x"
v-model:y="layer.alignY" v-model:y="layer.align.y"
></MkPositionSelector> ></MkPositionSelector>
</FormSlot> </FormSlot>
@ -50,8 +50,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSlot> <FormSlot>
<template #label>{{ i18n.ts._watermarkEditor.position }}</template> <template #label>{{ i18n.ts._watermarkEditor.position }}</template>
<MkPositionSelector <MkPositionSelector
v-model:x="layer.alignX" v-model:x="layer.align.x"
v-model:y="layer.alignY" v-model:y="layer.align.y"
></MkPositionSelector> ></MkPositionSelector>
</FormSlot> </FormSlot>
@ -80,6 +80,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="layer.repeat"> <MkSwitch v-model="layer.repeat">
<template #label>{{ i18n.ts._watermarkEditor.repeat }}</template> <template #label>{{ i18n.ts._watermarkEditor.repeat }}</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="layer.cover">
<template #label>{{ i18n.ts._watermarkEditor.cover }}</template>
</MkSwitch>
</template> </template>
</div> </div>
</template> </template>
@ -87,9 +91,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts"> <script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue'; import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import type { WatermarkerLayer, WatermarkPreset } from '@/utility/watermarker.js'; import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
import type { WatermarkPreset } from '@/utility/watermark.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { Watermarker } from '@/utility/watermarker.js';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
@ -102,7 +106,7 @@ import { selectFile } from '@/utility/drive.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
const layer = defineModel<WatermarkerLayer>('layer', { required: true }); const layer = defineModel<WatermarkPreset['layers'][number]>('layer', { required: true });
const driveFile = ref(); const driveFile = ref();
const driveFileError = ref(false); const driveFileError = ref(false);

View File

@ -45,27 +45,23 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts"> <script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue'; import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import type { WatermarkPreset } from '@/utility/watermarker.js'; import type { WatermarkPreset } from '@/utility/watermark.js';
import { makeImageEffectorLayers } from '@/utility/watermark.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { Watermarker } from '@/utility/watermarker.js'; import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSlot from '@/components/form/slot.vue';
import XLayer from '@/components/MkWatermarkEditorDialog.Layer.vue'; import XLayer from '@/components/MkWatermarkEditorDialog.Layer.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { selectFile } from '@/utility/drive.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js';
import { deepClone } from '@/utility/clone.js'; import { deepClone } from '@/utility/clone.js';
import { ensureSignin } from '@/i.js'; import { ensureSignin } from '@/i.js';
const $i = ensureSignin(); const $i = ensureSignin();
const props = defineProps<{ const props = defineProps<{
preset: WatermarkPreset | null; preset?: WatermarkPreset | null;
}>(); }>();
const preset = reactive(deepClone(props.preset) ?? { const preset = reactive(deepClone(props.preset) ?? {
@ -75,8 +71,7 @@ const preset = reactive(deepClone(props.preset) ?? {
id: uuid(), id: uuid(),
type: 'text', type: 'text',
text: `(c) @${$i.username}`, text: `(c) @${$i.username}`,
alignX: 'right', align: { x: 'right', y: 'bottom' },
alignY: 'bottom',
scale: 0.3, scale: 0.3,
opacity: 0.75, opacity: 0.75,
repeat: false, repeat: false,
@ -101,10 +96,9 @@ watch(type, () => {
if (type.value === 'text') { if (type.value === 'text') {
preset.layers = [{ preset.layers = [{
id: uuid(), id: uuid(),
type: type.value, type: 'text',
text: `(c) @${$i.username}`, text: `(c) @${$i.username}`,
alignX: 'right', align: { x: 'right', y: 'bottom' },
alignY: 'bottom',
scale: 0.3, scale: 0.3,
opacity: 0.75, opacity: 0.75,
repeat: false, repeat: false,
@ -112,11 +106,10 @@ watch(type, () => {
} else if (type.value === 'image') { } else if (type.value === 'image') {
preset.layers = [{ preset.layers = [{
id: uuid(), id: uuid(),
type: type.value, type: 'image',
imageId: null, imageId: null,
imageUrl: null, imageUrl: null,
alignX: 'right', align: { x: 'right', y: 'bottom' },
alignY: 'bottom',
scale: 0.3, scale: 0.3,
opacity: 0.75, opacity: 0.75,
repeat: false, repeat: false,
@ -126,7 +119,7 @@ watch(type, () => {
watch(preset, async (newValue, oldValue) => { watch(preset, async (newValue, oldValue) => {
if (renderer != null) { if (renderer != null) {
renderer.updatePreset(preset); renderer.updateLayers(makeImageEffectorLayers(preset.layers));
} }
}, { deep: true }); }, { deep: true });
@ -153,25 +146,25 @@ watch(sampleImageType, async () => {
} }
}); });
let renderer: Watermarker | null = null; let renderer: ImageEffector | null = null;
async function initRenderer() { async function initRenderer() {
if (canvasEl.value == null) return; if (canvasEl.value == null) return;
if (sampleImageType.value === '3_2') { if (sampleImageType.value === '3_2') {
renderer = new Watermarker({ renderer = new ImageEffector({
canvas: canvasEl.value, canvas: canvasEl.value,
width: 1500, width: 1500,
height: 1000, height: 1000,
preset: preset, layers: makeImageEffectorLayers(preset.layers),
originalImage: sampleImage_3_2, originalImage: sampleImage_3_2,
}); });
} else if (sampleImageType.value === '2_3') { } else if (sampleImageType.value === '2_3') {
renderer = new Watermarker({ renderer = new ImageEffector({
canvas: canvasEl.value, canvas: canvasEl.value,
width: 1000, width: 1000,
height: 1500, height: 1500,
preset: preset, layers: makeImageEffectorLayers(preset.layers),
originalImage: sampleImage_2_3, originalImage: sampleImage_2_3,
}); });
} }

View File

@ -146,6 +146,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
<template v-if="urlPreviewForm.state.urlPreviewEnabled"> <template v-if="urlPreviewForm.state.urlPreviewEnabled">
<MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
<template #label>{{ i18n.ts._urlPreviewSetting.allowRedirect }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
<template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
</MkSwitch>
<MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength"> <MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template> <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template> <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
@ -288,7 +293,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, reactive } from 'vue'; import { computed } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -301,7 +306,6 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { useForm } from '@/composables/use-form.js'; import { useForm } from '@/composables/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue'; import MkFormFooter from '@/components/MkFormFooter.vue';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
@ -370,6 +374,7 @@ const adForm = useForm({
const urlPreviewForm = useForm({ const urlPreviewForm = useForm({
urlPreviewEnabled: meta.urlPreviewEnabled, urlPreviewEnabled: meta.urlPreviewEnabled,
urlPreviewAllowRedirect: meta.urlPreviewAllowRedirect,
urlPreviewTimeout: meta.urlPreviewTimeout, urlPreviewTimeout: meta.urlPreviewTimeout,
urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength, urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength,
@ -378,6 +383,7 @@ const urlPreviewForm = useForm({
}, async (state) => { }, async (state) => {
await os.apiWithDialog('admin/update-meta', { await os.apiWithDialog('admin/update-meta', {
urlPreviewEnabled: state.urlPreviewEnabled, urlPreviewEnabled: state.urlPreviewEnabled,
urlPreviewAllowRedirect: state.urlPreviewAllowRedirect,
urlPreviewTimeout: state.urlPreviewTimeout, urlPreviewTimeout: state.urlPreviewTimeout,
urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: state.urlPreviewRequireContentLength, urlPreviewRequireContentLength: state.urlPreviewRequireContentLength,

View File

@ -22,13 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'; import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import type { WatermarkPreset } from '@/utility/watermarker.js'; import type { WatermarkPreset } from '@/utility/watermark.js';
import { makeImageEffectorLayers } from '@/utility/watermark.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 { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { deepClone } from '@/utility/clone.js'; import { deepClone } from '@/utility/clone.js';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import { Watermarker } from '@/utility/watermarker.js'; import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
const props = defineProps<{ const props = defineProps<{
preset: WatermarkPreset; preset: WatermarkPreset;
@ -64,18 +65,18 @@ const canvasEl = useTemplateRef('canvasEl');
const sampleImage = new Image(); const sampleImage = new Image();
sampleImage.src = '/client-assets/sample/3-2.jpg'; sampleImage.src = '/client-assets/sample/3-2.jpg';
let renderer: Watermarker | null = null; let renderer: ImageEffector | null = null;
onMounted(() => { onMounted(() => {
sampleImage.onload = async () => { sampleImage.onload = async () => {
watch(canvasEl, async () => { watch(canvasEl, async () => {
if (canvasEl.value == null) return; if (canvasEl.value == null) return;
renderer = new Watermarker({ renderer = new ImageEffector({
canvas: canvasEl.value, canvas: canvasEl.value,
width: 1500, width: 1500,
height: 1000, height: 1000,
preset: props.preset, layers: makeImageEffectorLayers(props.preset.layers),
originalImage: sampleImage, originalImage: sampleImage,
}); });
@ -95,7 +96,7 @@ onUnmounted(() => {
watch(() => props.preset, async () => { watch(() => props.preset, async () => {
if (renderer != null) { if (renderer != null) {
renderer.updatePreset(props.preset); renderer.updateLayers(makeImageEffectorLayers(props.preset.layers));
await renderer.bakeTextures(); await renderer.bakeTextures();
renderer.render(); renderer.render();
} }

View File

@ -39,62 +39,107 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection> </FormSection>
</SearchMarker> </SearchMarker>
<FormSection> <SearchMarker :keywords="['general']">
<div class="_gaps_m"> <FormSection>
<SearchMarker :keywords="['default', 'upload', 'folder']"> <template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template>
<FormLink @click="chooseUploadFolder()">
<SearchLabel>{{ i18n.ts.uploadFolder }}</SearchLabel> <div class="_gaps_m">
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> <SearchMarker :keywords="['default', 'upload', 'folder']">
<template #suffixIcon><i class="ti ti-folder"></i></template> <FormLink @click="chooseUploadFolder()">
<SearchLabel>{{ i18n.ts.uploadFolder }}</SearchLabel>
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
<template #suffixIcon><i class="ti ti-folder"></i></template>
</FormLink>
</SearchMarker>
<FormLink to="/settings/drive/cleaner">
{{ i18n.ts.drivecleaner }}
</FormLink> </FormLink>
</SearchMarker>
<FormLink to="/settings/drive/cleaner"> <SearchMarker :keywords="['keep', 'original', 'filename']">
{{ i18n.ts.drivecleaner }} <MkPreferenceContainer k="keepOriginalFilename">
</FormLink> <MkSwitch v-model="keepOriginalFilename">
<template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['watermark', 'credit']"> <SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
<MkFolder> <MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
<template #icon><i class="ti ti-copyright"></i></template> <template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template>
<template #label><SearchLabel>{{ i18n.ts.watermark }}</SearchLabel></template>
<div class="_gaps_s">
<XWatermarkItem
v-for="(preset, i) in prefer.r.watermarkPresets.value"
:key="preset.id"
:preset="preset"
@updatePreset="onUpdateWatermarkPreset(preset.id, $event)"
@del="onDeleteWatermarkPreset(preset.id)"
/>
<MkButton iconOnly rounded style="margin: 0 auto;" @click="addWatermarkPreset"><i class="ti ti-plus"></i></MkButton>
</div>
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['keep', 'original', 'filename']">
<MkPreferenceContainer k="keepOriginalFilename">
<MkSwitch v-model="keepOriginalFilename">
<template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
</MkSwitch> </MkSwitch>
</MkPreferenceContainer> </SearchMarker>
</SearchMarker>
<SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']"> <SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']">
<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()"> <MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
<template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
</MkSwitch> <template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
</SearchMarker> </MkSwitch>
</SearchMarker>
</div>
</FormSection>
</SearchMarker>
<SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']"> <SearchMarker :keywords="['image']">
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()"> <FormSection>
<template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template> <template #label><SearchLabel>{{ i18n.ts.image }}</SearchLabel></template>
<template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
</MkSwitch> <div class="_gaps_m">
</SearchMarker> <SearchMarker :keywords="['watermark', 'credit']">
</div> <MkFolder>
</FormSection> <template #icon><i class="ti ti-copyright"></i></template>
<template #label><SearchLabel>{{ i18n.ts.watermark }}</SearchLabel></template>
<div class="_gaps">
<div class="_gaps_s">
<XWatermarkItem
v-for="(preset, i) in prefer.r.watermarkPresets.value"
:key="preset.id"
:preset="preset"
@updatePreset="onUpdateWatermarkPreset(preset.id, $event)"
@del="onDeleteWatermarkPreset(preset.id)"
/>
<MkButton iconOnly rounded style="margin: 0 auto;" @click="addWatermarkPreset"><i class="ti ti-plus"></i></MkButton>
<SearchMarker :keywords="['sync', 'watermark', 'preset', 'devices']">
<MkSwitch :modelValue="watermarkPresetsSyncEnabled" @update:modelValue="changeWatermarkPresetsSyncEnabled">
<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts.syncBetweenDevices }}</SearchLabel></template>
</MkSwitch>
</SearchMarker>
</div>
<hr>
<SearchMarker :keywords="['default', 'watermark', 'preset']">
<MkPreferenceContainer k="defaultWatermarkPresetId">
<MkSelect v-model="defaultWatermarkPresetId" :items="[{ label: i18n.ts.none, value: null }, ...prefer.r.watermarkPresets.value.map(p => ({ label: p.name || i18n.ts.noName, value: p.id }))]">
<template #label><SearchLabel>{{ i18n.ts.defaultPreset }}</SearchLabel></template>
</MkSelect>
</MkPreferenceContainer>
</SearchMarker>
</div>
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['default', 'image', 'compression']">
<MkPreferenceContainer k="defaultImageCompressionLevel">
<MkSelect
v-model="defaultImageCompressionLevel" :items="[
{ label: i18n.ts.none, value: 0 },
{ label: i18n.ts.low, value: 1 },
{ label: i18n.ts.medium, value: 2 },
{ label: i18n.ts.high, value: 3 },
]"
>
<template #label><SearchLabel>{{ i18n.ts.defaultImageCompressionLevel }}</SearchLabel></template>
</MkSelect>
</MkPreferenceContainer>
</SearchMarker>
</div>
</FormSection>
</SearchMarker>
</div> </div>
</SearchMarker> </SearchMarker>
</template> </template>
@ -104,9 +149,10 @@ import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import XWatermarkItem from './drive.WatermarkItem.vue'; import XWatermarkItem from './drive.WatermarkItem.vue';
import type { WatermarkPreset } from '@/utility/watermarker.js'; import type { WatermarkPreset } from '@/utility/watermark.js';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
@ -146,6 +192,22 @@ const meterStyle = computed(() => {
}); });
const keepOriginalFilename = prefer.model('keepOriginalFilename'); const keepOriginalFilename = prefer.model('keepOriginalFilename');
const defaultWatermarkPresetId = prefer.model('defaultWatermarkPresetId');
const defaultImageCompressionLevel = prefer.model('defaultImageCompressionLevel');
const watermarkPresetsSyncEnabled = ref(prefer.isSyncEnabled('watermarkPresets'));
function changeWatermarkPresetsSyncEnabled(value: boolean) {
if (value) {
prefer.enableSync('watermarkPresets').then((res) => {
if (res == null) return;
if (res.enabled) watermarkPresetsSyncEnabled.value = true;
});
} else {
prefer.disableSync('watermarkPresets');
watermarkPresetsSyncEnabled.value = false;
}
}
misskeyApi('drive').then(info => { misskeyApi('drive').then(info => {
capacity.value = info.capacity; capacity.value = info.capacity;
@ -203,6 +265,10 @@ function onDeleteWatermarkPreset(id: string) {
...prefer.s.watermarkPresets.slice(0, index), ...prefer.s.watermarkPresets.slice(0, index),
...prefer.s.watermarkPresets.slice(index + 1), ...prefer.s.watermarkPresets.slice(index + 1),
]); ]);
if (prefer.s.defaultWatermarkPresetId === id) {
prefer.commit('defaultWatermarkPresetId', null);
}
} }
} }

View File

@ -11,7 +11,7 @@ import type { Plugin } from '@/plugin.js';
import type { DeviceKind } from '@/utility/device-kind.js'; import type { DeviceKind } from '@/utility/device-kind.js';
import type { DeckProfile } from '@/deck.js'; import type { DeckProfile } from '@/deck.js';
import type { PreferencesDefinition } from './manager.js'; import type { PreferencesDefinition } from './manager.js';
import type { WatermarkPreset } from '@/utility/watermarker.js'; import type { WatermarkPreset } from '@/utility/watermark.js';
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
/** サウンド設定 */ /** サウンド設定 */
@ -351,8 +351,16 @@ export const PREF_DEF = {
default: [] as string[], default: [] as string[],
}, },
watermarkPresets: { watermarkPresets: {
accountDependent: true,
default: [] as WatermarkPreset[], default: [] as WatermarkPreset[],
}, },
defaultWatermarkPresetId: {
accountDependent: true,
default: null as WatermarkPreset['id'] | null,
},
defaultImageCompressionLevel: {
default: 2,
},
'sound.masterVolume': { 'sound.masterVolume': {
default: 0.5, default: 0.5,

View File

@ -3,86 +3,72 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
const IMAGE_ADD_SHADER = `#version 300 es import { getProxiedImageUrl } from '../media-proxy.js';
precision highp float; import { FX_chromaticAberration } from './fxs/chromaticAberration.js';
import { FX_glitch } from './fxs/glitch.js';
import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js';
in vec2 in_uv; type ParamTypeToPrimitive = {
uniform sampler2D u_texture_src; 'number': number;
uniform sampler2D u_texture_watermark; 'boolean': boolean;
uniform vec2 u_resolution_src; 'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; };
uniform vec2 u_resolution_watermark; 'seed': number;
uniform float u_scale; };
uniform float u_angle;
uniform float u_opacity;
uniform bool u_repeat;
uniform int u_alignX; // 0: left, 1: center, 2: right
uniform int u_alignY; // 0: top, 1: center, 2: bottom
out vec4 out_color;
void main() { type ImageEffectorFxParamDefs = Record<string, {
vec4 pixel = texture(u_texture_src, in_uv); type: keyof ParamTypeToPrimitive;
default: any;
}>;
float x_ratio = u_resolution_watermark.x / u_resolution_src.x; export function defineImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDefs>(fx: ImageEffectorFx<ID, P>) {
float y_ratio = u_resolution_watermark.y / u_resolution_src.y; return fx;
float aspect_ratio = min(x_ratio, y_ratio) / max(x_ratio, y_ratio);
float x_scale = x_ratio > y_ratio ? 1.0 * u_scale : aspect_ratio * u_scale;
float y_scale = y_ratio > x_ratio ? 1.0 * u_scale : aspect_ratio * u_scale;
float x_offset = u_alignX == 0 ? x_scale / 2.0 : u_alignX == 2 ? 1.0 - (x_scale / 2.0) : 0.5;
float y_offset = u_alignY == 0 ? y_scale / 2.0 : u_alignY == 2 ? 1.0 - (y_scale / 2.0) : 0.5;
if (!u_repeat) {
bool isInside = in_uv.x > x_offset - (x_scale / 2.0) && in_uv.x < x_offset + (x_scale / 2.0) &&
in_uv.y > y_offset - (y_scale / 2.0) && in_uv.y < y_offset + (y_scale / 2.0);
if (!isInside) {
out_color = pixel;
return;
}
}
vec4 watermarkPixel = texture(u_texture_watermark, vec2(
(in_uv.x - (x_offset - (x_scale / 2.0))) / x_scale,
(in_uv.y - (y_offset - (y_scale / 2.0))) / y_scale
));
out_color.r = mix(pixel.r, watermarkPixel.r, u_opacity * watermarkPixel.a);
out_color.g = mix(pixel.g, watermarkPixel.g, u_opacity * watermarkPixel.a);
out_color.b = mix(pixel.b, watermarkPixel.b, u_opacity * watermarkPixel.a);
out_color.a = pixel.a * (1.0 - u_opacity * watermarkPixel.a) + watermarkPixel.a * u_opacity;
} }
`;
export type WatermarkPreset = { export type ImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDefs> = {
id: string; id: ID;
name: string; shader: string;
layers: WatermarkerLayer[]; params: P,
main: (ctx: {
gl: WebGL2RenderingContext;
program: WebGLProgram;
params: {
[key in keyof P]: ParamTypeToPrimitive[P[key]['type']];
};
preTexture: WebGLTexture;
width: number;
height: number;
watermark?: {
texture: WebGLTexture;
width: number;
height: number;
};
}) => void;
}; };
type WatermarkerTextLayer = { export const FXS = [
FX_watermarkPlacement,
FX_chromaticAberration,
FX_glitch,
] as const satisfies ImageEffectorFx<string, any>[];
export type ImageEffectorLayerOf<
FXID extends (typeof FXS)[number]['id'],
FX extends { params: ImageEffectorFxParamDefs } = Extract<(typeof FXS)[number], { id: FXID }>,
> = {
id: string; id: string;
type: 'text'; fxId: FXID;
text: string; params: {
repeat: boolean; [key in keyof FX['params']]: ParamTypeToPrimitive[FX['params'][key]['type']];
scale: number; };
alignX: 'left' | 'center' | 'right';
alignY: 'top' | 'center' | 'bottom'; // for watermarkPlacement fx
opacity: number; imageUrl?: string | null;
text?: string | null;
}; };
type WatermarkerImageLayer = { export type ImageEffectorLayer = ImageEffectorLayerOf<(typeof FXS)[number]['id'], Extract<(typeof FXS)[number], { id: (typeof FXS)[number]['id'] }>>;
id: string;
type: 'image';
imageUrl: string | null;
imageId: string | null;
repeat: boolean;
scale: number;
alignX: 'left' | 'center' | 'right';
alignY: 'top' | 'center' | 'bottom';
opacity: number;
};
export type WatermarkerLayer = WatermarkerTextLayer | WatermarkerImageLayer; export class ImageEffector {
export class Watermarker {
private canvas: HTMLCanvasElement | null = null; private canvas: HTMLCanvasElement | null = null;
private gl: WebGL2RenderingContext | null = null; private gl: WebGL2RenderingContext | null = null;
private renderTextureProgram!: WebGLProgram; private renderTextureProgram!: WebGLProgram;
@ -90,19 +76,20 @@ export class Watermarker {
private renderWidth!: number; private renderWidth!: number;
private renderHeight!: number; private renderHeight!: number;
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement; private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
private preset: WatermarkPreset; private layers: ImageEffectorLayer[];
private originalImageTexture: WebGLTexture; private originalImageTexture: WebGLTexture;
private resultTexture: WebGLTexture; private bakedTexturesForWatermarkFx: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
private resultFrameBuffer: WebGLFramebuffer;
private bakedTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
private texturesKey: string; private texturesKey: string;
private shaderCache: Map<string, WebGLProgram> = new Map();
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
constructor(options: { constructor(options: {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
width: number; width: number;
height: number; height: number;
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement; originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
preset: WatermarkPreset; layers: ImageEffectorLayer[];
}) { }) {
this.canvas = options.canvas; this.canvas = options.canvas;
this.canvas.width = options.width; this.canvas.width = options.width;
@ -110,7 +97,7 @@ export class Watermarker {
this.renderWidth = options.width; this.renderWidth = options.width;
this.renderHeight = options.height; this.renderHeight = options.height;
this.originalImage = options.originalImage; this.originalImage = options.originalImage;
this.preset = options.preset; this.layers = options.layers;
this.texturesKey = this.calcTexturesKey(); this.texturesKey = this.calcTexturesKey();
this.gl = this.canvas.getContext('webgl2', { this.gl = this.canvas.getContext('webgl2', {
@ -134,9 +121,6 @@ export class Watermarker {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, options.width, options.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, options.width, options.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
this.resultTexture = this.createTexture();
this.resultFrameBuffer = gl.createFramebuffer()!;
this.renderTextureProgram = this.initShaderProgram(`#version 300 es this.renderTextureProgram = this.initShaderProgram(`#version 300 es
in vec2 position; in vec2 position;
out vec2 in_uv; out vec2 in_uv;
@ -180,10 +164,10 @@ export class Watermarker {
} }
private calcTexturesKey() { private calcTexturesKey() {
return this.preset.layers.map(layer => { return this.layers.map(layer => {
if (layer.type === 'image') { if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
return layer.imageId; return layer.imageUrl;
} else if (layer.type === 'text') { } else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
return layer.text; return layer.text;
} }
return ''; return '';
@ -208,10 +192,10 @@ export class Watermarker {
throw new Error('gl is not initialized'); throw new Error('gl is not initialized');
} }
for (const bakedTexture of this.bakedTextures.values()) { for (const bakedTexture of this.bakedTexturesForWatermarkFx.values()) {
gl.deleteTexture(bakedTexture.texture); gl.deleteTexture(bakedTexture.texture);
} }
this.bakedTextures.clear(); this.bakedTexturesForWatermarkFx.clear();
} }
public async bakeTextures() { public async bakeTextures() {
@ -224,14 +208,13 @@ export class Watermarker {
this.disposeBakedTextures(); this.disposeBakedTextures();
for (const layer of this.preset.layers) { for (const layer of this.layers) {
if (layer.type === 'image') { if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
const image = await new Promise<HTMLImageElement>((resolve, reject) => { const image = await new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image(); const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img); img.onload = () => resolve(img);
img.onerror = reject; img.onerror = reject;
img.src = layer.imageUrl; img.src = getProxiedImageUrl(layer.imageUrl); // CORS対策
}); });
const texture = this.createTexture(); const texture = this.createTexture();
@ -240,12 +223,12 @@ export class Watermarker {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
this.bakedTextures.set(layer.id, { this.bakedTexturesForWatermarkFx.set(layer.id, {
texture: texture, texture: texture,
width: image.width, width: image.width,
height: image.height, height: image.height,
}); });
} else if (layer.type === 'text') { } else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
const measureCtx = window.document.createElement('canvas').getContext('2d')!; const measureCtx = window.document.createElement('canvas').getContext('2d')!;
measureCtx.canvas.width = this.renderWidth; measureCtx.canvas.width = this.renderWidth;
measureCtx.canvas.height = this.renderHeight; measureCtx.canvas.height = this.renderHeight;
@ -280,7 +263,7 @@ export class Watermarker {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textCtx.canvas.width, textCtx.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, textCtx.canvas); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textCtx.canvas.width, textCtx.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, textCtx.canvas);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
this.bakedTextures.set(layer.id, { this.bakedTexturesForWatermarkFx.set(layer.id, {
texture: texture, texture: texture,
width: textCtx.canvas.width, width: textCtx.canvas.width,
height: textCtx.canvas.height, height: textCtx.canvas.height,
@ -333,18 +316,19 @@ export class Watermarker {
return shaderProgram; return shaderProgram;
} }
private renderTextOrImageLayer(layer: WatermarkerTextLayer | WatermarkerImageLayer) { private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
const gl = this.gl; const gl = this.gl;
if (gl == null) { if (gl == null) {
throw new Error('gl is not initialized'); throw new Error('gl is not initialized');
} }
const watermarkTexture = this.bakedTextures.get(layer.id); const fx = FXS.find(fx => fx.id === layer.fxId);
if (watermarkTexture == null) { if (fx == null) return;
return;
}
const shaderProgram = this.initShaderProgram(`#version 300 es const watermark = layer.fxId === 'watermarkPlacement' ? this.bakedTexturesForWatermarkFx.get(layer.id) : undefined;
const cachedShader = this.shaderCache.get(fx.id);
const shaderProgram = cachedShader ?? this.initShaderProgram(`#version 300 es
in vec2 position; in vec2 position;
out vec2 in_uv; out vec2 in_uv;
@ -352,72 +336,36 @@ export class Watermarker {
in_uv = (position + 1.0) / 2.0; in_uv = (position + 1.0) / 2.0;
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
`, IMAGE_ADD_SHADER); `, fx.shader);
if (cachedShader == null) {
this.shaderCache.set(fx.id, shaderProgram);
}
gl.useProgram(shaderProgram); gl.useProgram(shaderProgram);
gl.activeTexture(gl.TEXTURE0); fx.main({
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); gl: gl,
const u_texture_src = gl.getUniformLocation(shaderProgram, 'u_texture_src'); program: shaderProgram,
gl.uniform1i(u_texture_src, 0); params: Object.fromEntries(
Object.entries(fx.params).map(([key, param]) => {
gl.activeTexture(gl.TEXTURE1); return [key, layer.params[key] ?? param.default];
gl.bindTexture(gl.TEXTURE_2D, watermarkTexture.texture); }),
const u_texture_watermark = gl.getUniformLocation(shaderProgram, 'u_texture_watermark'); ) as any,
gl.uniform1i(u_texture_watermark, 1); preTexture: preTexture,
width: this.renderWidth,
const u_resolution_src = gl.getUniformLocation(shaderProgram, 'u_resolution_src'); height: this.renderHeight,
gl.uniform2fv(u_resolution_src, [this.renderWidth, this.renderHeight]); watermark: watermark,
});
const u_resolution_watermark = gl.getUniformLocation(shaderProgram, 'u_resolution_watermark');
gl.uniform2fv(u_resolution_watermark, [watermarkTexture.width, watermarkTexture.height]);
const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale');
gl.uniform1f(u_scale, layer.scale);
const u_opacity = gl.getUniformLocation(shaderProgram, 'u_opacity');
gl.uniform1f(u_opacity, layer.opacity);
const u_angle = gl.getUniformLocation(shaderProgram, 'u_angle');
gl.uniform1f(u_angle, 0.0);
const u_repeat = gl.getUniformLocation(shaderProgram, 'u_repeat');
gl.uniform1i(u_repeat, layer.repeat ? 1 : 0);
const u_alignX = gl.getUniformLocation(shaderProgram, 'u_alignX');
gl.uniform1i(u_alignX, layer.alignX === 'left' ? 0 : layer.alignX === 'right' ? 2 : 1);
const u_alignY = gl.getUniformLocation(shaderProgram, 'u_alignY');
gl.uniform1i(u_alignY, layer.alignY === 'top' ? 0 : layer.alignY === 'bottom' ? 2 : 1);
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 0, 6);
} }
private renderLayer(layer: WatermarkerLayer) {
if (layer.type === 'image') {
this.renderTextOrImageLayer(layer);
} else if (layer.type === 'text') {
this.renderTextOrImageLayer(layer);
}
}
public render() { public render() {
const gl = this.gl; const gl = this.gl;
if (gl == null) { if (gl == null) {
throw new Error('gl is not initialized'); throw new Error('gl is not initialized');
} }
gl.bindTexture(gl.TEXTURE_2D, this.resultTexture);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, this.resultFrameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.resultTexture, 0);
// --------------------
{ {
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
@ -436,8 +384,31 @@ export class Watermarker {
// -------------------- // --------------------
for (const layer of this.preset.layers) { let preTexture = this.originalImageTexture;
this.renderLayer(layer);
for (const layer of this.layers) {
const cachedResultTexture = this.perLayerResultTextures.get(layer.id);
const resultTexture = cachedResultTexture ?? this.createTexture();
if (cachedResultTexture == null) {
this.perLayerResultTextures.set(layer.id, resultTexture);
}
gl.bindTexture(gl.TEXTURE_2D, resultTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_2D, null);
const cachedResultFrameBuffer = this.perLayerResultFrameBuffers.get(layer.id);
const resultFrameBuffer = cachedResultFrameBuffer ?? gl.createFramebuffer()!;
if (cachedResultFrameBuffer == null) {
this.perLayerResultFrameBuffers.set(layer.id, resultFrameBuffer);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, resultFrameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultTexture, 0);
this.renderLayer(layer, preTexture);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
preTexture = resultTexture;
} }
// -------------------- // --------------------
@ -446,13 +417,13 @@ export class Watermarker {
gl.useProgram(this.renderInvertedTextureProgram); gl.useProgram(this.renderInvertedTextureProgram);
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.resultTexture); gl.bindTexture(gl.TEXTURE_2D, preTexture);
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 0, 6);
} }
public async updatePreset(preset: WatermarkPreset) { public async updateLayers(layers: ImageEffectorLayer[]) {
this.preset = preset; this.layers = layers;
const newTexturesKey = this.calcTexturesKey(); const newTexturesKey = this.calcTexturesKey();
if (newTexturesKey !== this.texturesKey) { if (newTexturesKey !== this.texturesKey) {
@ -469,11 +440,24 @@ export class Watermarker {
throw new Error('gl is not initialized'); throw new Error('gl is not initialized');
} }
for (const shader of this.shaderCache.values()) {
gl.deleteProgram(shader);
}
this.shaderCache.clear();
for (const texture of this.perLayerResultTextures.values()) {
gl.deleteTexture(texture);
}
this.perLayerResultTextures.clear();
for (const framebuffer of this.perLayerResultFrameBuffers.values()) {
gl.deleteFramebuffer(framebuffer);
}
this.perLayerResultFrameBuffers.clear();
this.disposeBakedTextures(); this.disposeBakedTextures();
gl.deleteProgram(this.renderTextureProgram); gl.deleteProgram(this.renderTextureProgram);
gl.deleteProgram(this.renderInvertedTextureProgram); gl.deleteProgram(this.renderInvertedTextureProgram);
gl.deleteTexture(this.originalImageTexture); gl.deleteTexture(this.originalImageTexture);
gl.deleteTexture(this.resultTexture);
gl.deleteFramebuffer(this.resultFrameBuffer);
} }
} }

View File

@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.js';
const shader = `#version 300 es
precision highp float;
in vec2 in_uv;
uniform sampler2D u_texture;
uniform vec2 u_resolution;
out vec4 out_color;
uniform float u_amount;
uniform float u_start;
uniform bool u_normalize;
void main() {
int samples = 64;
float r_strength = 1.0;
float g_strength = 1.5;
float b_strength = 2.0;
vec2 size = vec2(u_resolution.x, u_resolution.y);
vec4 accumulator = vec4(0.0);
float normalisedValue = length((in_uv - 0.5) * 2.0);
float strength = clamp((normalisedValue - u_start) * (1.0 / (1.0 - u_start)), 0.0, 1.0);
vec2 vector = (u_normalize ? normalize(in_uv - vec2(0.5)) : in_uv - vec2(0.5));
vec2 velocity = vector * strength * u_amount;
vec2 rOffset = -vector * strength * (u_amount * r_strength);
vec2 gOffset = -vector * strength * (u_amount * g_strength);
vec2 bOffset = -vector * strength * (u_amount * b_strength);
for (int i = 0; i < samples; i++) {
accumulator.r += texture(u_texture, in_uv + rOffset).r;
rOffset -= velocity / float(samples);
accumulator.g += texture(u_texture, in_uv + gOffset).g;
gOffset -= velocity / float(samples);
accumulator.b += texture(u_texture, in_uv + bOffset).b;
bOffset -= velocity / float(samples);
}
out_color = vec4(vec3(accumulator / float(samples)), 1.0);
}
`;
export const FX_chromaticAberration = defineImageEffectorFx({
id: 'chromaticAberration' as const,
shader,
params: {
normalize: {
type: 'boolean' as const,
default: false,
},
amount: {
type: 'number' as const,
default: 0.1,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, program, params, preTexture }) => {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, preTexture);
const u_texture = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(u_texture, 0);
const u_amount = gl.getUniformLocation(program, 'u_amount');
gl.uniform1f(u_amount, params.amount);
const u_normalize = gl.getUniformLocation(program, 'u_normalize');
gl.uniform1i(u_normalize, params.normalize ? 1 : 0);
},
});

View File

@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import seedrandom from 'seedrandom';
import { defineImageEffectorFx } from '../ImageEffector.js';
const shader = `#version 300 es
precision highp float;
in vec2 in_uv;
uniform sampler2D u_texture;
uniform vec2 u_resolution;
uniform int u_amount;
uniform float u_shiftStrengths[128];
uniform float u_shiftOrigins[128];
uniform float u_shiftHeights[128];
uniform float u_channelShift;
out vec4 out_color;
void main() {
float v = 0.0;
for (int i = 0; i < u_amount; i++) {
if (in_uv.y > (u_shiftOrigins[i] - u_shiftHeights[i]) && in_uv.y < (u_shiftOrigins[i] + u_shiftHeights[i])) {
v += u_shiftStrengths[i];
}
}
float r = texture(u_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r;
float g = texture(u_texture, vec2(in_uv.x + v, in_uv.y)).g;
float b = texture(u_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b;
float a = texture(u_texture, vec2(in_uv.x + v, in_uv.y)).a;
out_color = vec4(r, g, b, a);
}
`;
export const FX_glitch = defineImageEffectorFx({
id: 'glitch' as const,
shader,
params: {
amount: {
type: 'number' as const,
default: 3,
min: 1,
max: 100,
step: 1,
},
strength: {
type: 'number' as const,
default: 5,
min: -100,
max: 100,
step: 0.01,
},
size: {
type: 'number' as const,
default: 20,
min: 0,
max: 100,
step: 0.01,
},
channelShift: {
type: 'number' as const,
default: 0.5,
min: 0,
max: 10,
step: 0.01,
},
seed: {
type: 'seed' as const,
default: 100,
},
},
main: ({ gl, program, params, preTexture }) => {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, preTexture);
const u_texture = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(u_texture, 0);
const u_amount = gl.getUniformLocation(program, 'u_amount');
gl.uniform1i(u_amount, params.amount);
const u_channelShift = gl.getUniformLocation(program, 'u_channelShift');
gl.uniform1f(u_channelShift, params.channelShift);
const rnd = seedrandom(params.seed.toString());
for (let i = 0; i < params.amount; i++) {
const o = gl.getUniformLocation(program, `u_shiftOrigins[${i.toString()}]`);
gl.uniform1f(o, rnd());
const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`);
gl.uniform1f(s, (1 - (rnd() * 2)) * (params.strength / 100));
const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`);
gl.uniform1f(h, rnd() * (params.size / 100));
}
},
});

View File

@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.js';
const shader = `#version 300 es
precision highp float;
in vec2 in_uv;
uniform sampler2D u_texture_src;
uniform sampler2D u_texture_watermark;
uniform vec2 u_resolution_src;
uniform vec2 u_resolution_watermark;
uniform float u_scale;
uniform float u_angle;
uniform float u_opacity;
uniform bool u_repeat;
uniform int u_alignX; // 0: left, 1: center, 2: right
uniform int u_alignY; // 0: top, 1: center, 2: bottom
uniform int u_fitMode; // 0: contain, 1: cover
out vec4 out_color;
void main() {
vec4 pixel = texture(u_texture_src, in_uv);
bool contain = u_fitMode == 0;
float x_ratio = u_resolution_watermark.x / u_resolution_src.x;
float y_ratio = u_resolution_watermark.y / u_resolution_src.y;
float aspect_ratio = contain ?
(min(x_ratio, y_ratio) / max(x_ratio, y_ratio)) :
(max(x_ratio, y_ratio) / min(x_ratio, y_ratio));
float x_scale = contain ?
(x_ratio > y_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
(x_ratio > y_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
float y_scale = contain ?
(y_ratio > x_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
(y_ratio > x_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
float x_offset = u_alignX == 0 ? x_scale / 2.0 : u_alignX == 2 ? 1.0 - (x_scale / 2.0) : 0.5;
float y_offset = u_alignY == 0 ? y_scale / 2.0 : u_alignY == 2 ? 1.0 - (y_scale / 2.0) : 0.5;
if (!u_repeat) {
bool isInside = in_uv.x > x_offset - (x_scale / 2.0) && in_uv.x < x_offset + (x_scale / 2.0) &&
in_uv.y > y_offset - (y_scale / 2.0) && in_uv.y < y_offset + (y_scale / 2.0);
if (!isInside) {
out_color = pixel;
return;
}
}
vec4 watermarkPixel = texture(u_texture_watermark, vec2(
(in_uv.x - (x_offset - (x_scale / 2.0))) / x_scale,
(in_uv.y - (y_offset - (y_scale / 2.0))) / y_scale
));
out_color.r = mix(pixel.r, watermarkPixel.r, u_opacity * watermarkPixel.a);
out_color.g = mix(pixel.g, watermarkPixel.g, u_opacity * watermarkPixel.a);
out_color.b = mix(pixel.b, watermarkPixel.b, u_opacity * watermarkPixel.a);
out_color.a = pixel.a * (1.0 - u_opacity * watermarkPixel.a) + watermarkPixel.a * u_opacity;
}
`;
export const FX_watermarkPlacement = defineImageEffectorFx({
id: 'watermarkPlacement' as const,
shader,
params: {
cover: {
type: 'boolean' as const,
default: false,
},
repeat: {
type: 'boolean' as const,
default: false,
},
scale: {
type: 'number' as const,
default: 0.3,
min: 0.0,
max: 1.0,
step: 0.01,
},
align: {
type: 'align' as const,
default: { x: 'right', y: 'bottom' },
},
opacity: {
type: 'number' as const,
default: 0.75,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, program, params, preTexture, width, height, watermark }) => {
if (watermark == null) {
return;
}
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, preTexture);
const u_texture_src = gl.getUniformLocation(program, 'u_texture_src');
gl.uniform1i(u_texture_src, 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, watermark.texture);
const u_texture_watermark = gl.getUniformLocation(program, 'u_texture_watermark');
gl.uniform1i(u_texture_watermark, 1);
const u_resolution_src = gl.getUniformLocation(program, 'u_resolution_src');
gl.uniform2fv(u_resolution_src, [width, height]);
const u_resolution_watermark = gl.getUniformLocation(program, 'u_resolution_watermark');
gl.uniform2fv(u_resolution_watermark, [watermark.width, watermark.height]);
const u_scale = gl.getUniformLocation(program, 'u_scale');
gl.uniform1f(u_scale, params.scale);
const u_opacity = gl.getUniformLocation(program, 'u_opacity');
gl.uniform1f(u_opacity, params.opacity);
const u_angle = gl.getUniformLocation(program, 'u_angle');
gl.uniform1f(u_angle, 0.0);
const u_repeat = gl.getUniformLocation(program, 'u_repeat');
gl.uniform1i(u_repeat, params.repeat ? 1 : 0);
const u_alignX = gl.getUniformLocation(program, 'u_alignX');
gl.uniform1i(u_alignX, params.align.x === 'left' ? 0 : params.align.x === 'right' ? 2 : 1);
const u_alignY = gl.getUniformLocation(program, 'u_alignY');
gl.uniform1i(u_alignY, params.align.y === 'top' ? 0 : params.align.y === 'bottom' ? 2 : 1);
const u_fitMode = gl.getUniformLocation(program, 'u_fitMode');
gl.uniform1i(u_fitMode, params.cover ? 1 : 0);
},
});

View File

@ -9,24 +9,12 @@ import { apiUrl } from '@@/js/config.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
export const pendingApiRequestsCount = ref(0); export const pendingApiRequestsCount = ref(0);
export type Endpoint = keyof Misskey.Endpoints;
export type Request<E extends Endpoint> = Misskey.Endpoints[E]['req'];
export type AnyRequest<E extends Endpoint | (string & unknown)> =
(E extends Endpoint ? Request<E> : never) | object;
export type Response<E extends Endpoint | (string & unknown), P extends AnyRequest<E>> =
E extends Endpoint
? P extends Request<E> ? Misskey.api.SwitchCaseResponseType<E, P> : never
: object;
// Implements Misskey.api.ApiClient.request // Implements Misskey.api.ApiClient.request
export function misskeyApi< export function misskeyApi<
ResT = void, ResT = void,
E extends Endpoint | NonNullable<string> = Endpoint, E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
P extends AnyRequest<E> = E extends Endpoint ? Request<E> : never, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
_ResT = ResT extends void ? Response<E, P> : ResT, _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
>( >(
endpoint: E, endpoint: E,
data: P & { i?: string | null; } = {} as any, data: P & { i?: string | null; } = {} as any,

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
export type WatermarkPreset = {
id: string;
name: string;
layers: ({
id: string;
type: 'text';
text: string;
repeat: boolean;
scale: number;
align: { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom' };
opacity: number;
} | {
id: string;
type: 'image';
imageUrl: string | null;
imageId: string | null;
cover: boolean;
repeat: boolean;
scale: number;
align: { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom' };
opacity: number;
})[];
};
export function makeImageEffectorLayers(layers: WatermarkPreset['layers']): ImageEffectorLayer[] {
return layers.map(layer => {
if (layer.type === 'text') {
return {
fxId: 'watermarkPlacement',
id: layer.id,
params: {
repeat: layer.repeat,
scale: layer.scale,
align: layer.align,
opacity: layer.opacity,
cover: false,
},
text: layer.text,
imageUrl: null,
};
} else {
return {
fxId: 'watermarkPlacement',
id: layer.id,
params: {
repeat: layer.repeat,
scale: layer.scale,
align: layer.align,
opacity: layer.opacity,
cover: layer.cover,
},
text: null,
imageUrl: layer.imageUrl ?? null,
};
}
});
}

View File

@ -81,9 +81,15 @@ export function getConfig(): UserConfig {
return { return {
base: '/vite/', base: '/vite/',
// The console is shared with backend, so clearing the console will also clear the backend log.
clearScreen: false,
server: { server: {
host, // The backend allows access from any addresses, so vite also allows access from any addresses.
host: '0.0.0.0',
allowedHosts: host ? [host] : undefined,
port: 5173, port: 5173,
strictPort: true,
hmr: { hmr: {
// バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される // バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される
// そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない

View File

@ -7306,8 +7306,9 @@ export type operations = {
content: { content: {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
fileId?: string; fileId: string;
url?: string; } | {
url: string;
}; };
}; };
}; };
@ -8093,10 +8094,12 @@ export type operations = {
admin___emoji___update: { admin___emoji___update: {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': ({
/** Format: misskey:id */ /** Format: misskey:id */
id?: string; id: string;
name?: string; } | {
name: string;
}) & ({
/** Format: misskey:id */ /** Format: misskey:id */
fileId?: string; fileId?: string;
/** @description Use `null` to reset the category. */ /** @description Use `null` to reset the category. */
@ -8106,7 +8109,7 @@ export type operations = {
isSensitive?: boolean; isSensitive?: boolean;
localOnly?: boolean; localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
}; });
}; };
}; };
responses: { responses: {
@ -8806,6 +8809,7 @@ export type operations = {
uri: string; uri: string;
version: string; version: string;
urlPreviewEnabled: boolean; urlPreviewEnabled: boolean;
urlPreviewAllowRedirect: boolean;
urlPreviewTimeout: number; urlPreviewTimeout: number;
urlPreviewMaximumContentLength: number; urlPreviewMaximumContentLength: number;
urlPreviewRequireContentLength: boolean; urlPreviewRequireContentLength: boolean;
@ -11533,6 +11537,7 @@ export type operations = {
/** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */ /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */
summalyProxy?: string | null; summalyProxy?: string | null;
urlPreviewEnabled?: boolean; urlPreviewEnabled?: boolean;
urlPreviewAllowRedirect?: boolean;
urlPreviewTimeout?: number; urlPreviewTimeout?: number;
urlPreviewMaximumContentLength?: number; urlPreviewMaximumContentLength?: number;
urlPreviewRequireContentLength?: boolean; urlPreviewRequireContentLength?: boolean;
@ -16996,8 +17001,9 @@ export type operations = {
content: { content: {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
fileId?: string; fileId: string;
url?: string; } | {
url: string;
}; };
}; };
}; };
@ -23096,9 +23102,10 @@ export type operations = {
content: { content: {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
tokenId?: string; tokenId: string;
token?: string | null; } | ({
}; token: string | null;
});
}; };
}; };
responses: { responses: {
@ -25784,7 +25791,12 @@ export type operations = {
'notes___search-by-tag': { 'notes___search-by-tag': {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': ({
tag: string;
} | {
/** @description The outer arrays are chained with OR, the inner arrays are chained with AND. */
query: string[][];
}) & ({
/** @default null */ /** @default null */
reply?: boolean | null; reply?: boolean | null;
/** @default null */ /** @default null */
@ -25802,10 +25814,7 @@ export type operations = {
untilId?: string; untilId?: string;
/** @default 10 */ /** @default 10 */
limit?: number; limit?: number;
tag?: string; });
/** @description The outer arrays are chained with OR, the inner arrays are chained with AND. */
query?: string[][];
};
}; };
}; };
responses: { responses: {
@ -26890,9 +26899,10 @@ export type operations = {
content: { content: {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
pageId?: string; pageId: string;
name?: string; } | {
username?: string; name: string;
username: string;
}; };
}; };
}; };
@ -28979,18 +28989,20 @@ export type operations = {
users___followers: { users___followers: {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': ({
/** Format: misskey:id */
userId: string;
} | ({
username: string;
/** @description The local host is represented with `null`. */
host: string | null;
})) & {
/** Format: misskey:id */ /** Format: misskey:id */
sinceId?: string; sinceId?: string;
/** Format: misskey:id */ /** Format: misskey:id */
untilId?: string; untilId?: string;
/** @default 10 */ /** @default 10 */
limit?: number; limit?: number;
/** Format: misskey:id */
userId?: string;
username?: string;
/** @description The local host is represented with `null`. */
host?: string | null;
}; };
}; };
}; };
@ -29042,20 +29054,22 @@ export type operations = {
users___following: { users___following: {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': ({
/** Format: misskey:id */
userId: string;
} | ({
username: string;
/** @description The local host is represented with `null`. */
host: string | null;
})) & ({
/** Format: misskey:id */ /** Format: misskey:id */
sinceId?: string; sinceId?: string;
/** Format: misskey:id */ /** Format: misskey:id */
untilId?: string; untilId?: string;
/** @default 10 */ /** @default 10 */
limit?: number; limit?: number;
/** Format: misskey:id */
userId?: string;
username?: string;
/** @description The local host is represented with `null`. */
host?: string | null;
birthday?: string | null; birthday?: string | null;
}; });
}; };
}; };
responses: { responses: {
@ -30337,13 +30351,15 @@ export type operations = {
'users___search-by-username-and-host': { 'users___search-by-username-and-host': {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': (({
username: string | null;
}) | ({
host: string | null;
})) & {
/** @default 10 */ /** @default 10 */
limit?: number; limit?: number;
/** @default true */ /** @default true */
detail?: boolean; detail?: boolean;
username?: string | null;
host?: string | null;
}; };
}; };
}; };
@ -30395,14 +30411,17 @@ export type operations = {
users___show: { users___show: {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': ({
/** Format: misskey:id */ /** Format: misskey:id */
userId?: string; userId: string;
userIds?: string[]; } | {
username?: string; userIds: string[];
} | {
username: string;
}) & ({
/** @description The local host is represented with `null`. */ /** @description The local host is represented with `null`. */
host?: string | null; host?: string | null;
}; });
}; };
}; };
responses: { responses: {

View File

@ -88,11 +88,11 @@ importers:
packages/backend: packages/backend:
dependencies: dependencies:
'@aws-sdk/client-s3': '@aws-sdk/client-s3':
specifier: 3.815.0 specifier: 3.817.0
version: 3.815.0 version: 3.817.0
'@aws-sdk/lib-storage': '@aws-sdk/lib-storage':
specifier: 3.815.0 specifier: 3.817.0
version: 3.815.0(@aws-sdk/client-s3@3.815.0) version: 3.817.0(@aws-sdk/client-s3@3.817.0)
'@discordapp/twemoji': '@discordapp/twemoji':
specifier: 15.1.0 specifier: 15.1.0
version: 15.1.0 version: 15.1.0
@ -130,14 +130,14 @@ importers:
specifier: 0.1.70 specifier: 0.1.70
version: 0.1.70 version: 0.1.70
'@nestjs/common': '@nestjs/common':
specifier: 11.1.1 specifier: 11.1.2
version: 11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2) version: 11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': '@nestjs/core':
specifier: 11.1.1 specifier: 11.1.2
version: 11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) version: 11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/testing': '@nestjs/testing':
specifier: 11.1.1 specifier: 11.1.2
version: 11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1)(@nestjs/platform-express@10.4.17) version: 11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)(@nestjs/platform-express@10.4.18)
'@peertube/http-signature': '@peertube/http-signature':
specifier: 1.7.0 specifier: 1.7.0
version: 1.7.0 version: 1.7.0
@ -364,8 +364,8 @@ importers:
specifier: 3.4.1 specifier: 3.4.1
version: 3.4.1 version: 3.4.1
re2: re2:
specifier: 1.21.5 specifier: 1.22.1
version: 1.21.5 version: 1.22.1
redis-info: redis-info:
specifier: 3.1.0 specifier: 3.1.0
version: 3.1.0 version: 3.1.0
@ -406,8 +406,8 @@ importers:
specifier: 2.1.0 specifier: 2.1.0
version: 2.1.0 version: 2.1.0
systeminformation: systeminformation:
specifier: 5.26.1 specifier: 5.27.1
version: 5.26.1 version: 5.27.1
tinycolor2: tinycolor2:
specifier: 1.6.0 specifier: 1.6.0
version: 1.6.0 version: 1.6.0
@ -446,8 +446,8 @@ importers:
specifier: 29.7.0 specifier: 29.7.0
version: 29.7.0 version: 29.7.0
'@nestjs/platform-express': '@nestjs/platform-express':
specifier: 10.4.17 specifier: 10.4.18
version: 10.4.17(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1) version: 10.4.18(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)
'@sentry/vue': '@sentry/vue':
specifier: 9.22.0 specifier: 9.22.0
version: 9.22.0(vue@3.5.14(typescript@5.8.3)) version: 9.22.0(vue@3.5.14(typescript@5.8.3))
@ -1570,51 +1570,51 @@ packages:
'@aws-crypto/util@5.2.0': '@aws-crypto/util@5.2.0':
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
'@aws-sdk/client-s3@3.815.0': '@aws-sdk/client-s3@3.817.0':
resolution: {integrity: sha512-tpJyXuGYIPHIu8G53jXQw3mN5ZK6LdL+tcEF3kRJuQ377Vbo+BSfqaizt9Qb3JuOGcNwCp83jd2LlmcXypN5fg==} resolution: {integrity: sha512-nZyjhlLMEXDs0ofWbpikI8tKoeKuuSgYcIb6eEZJk90Nt5HkkXn6nkWOs/kp2FdhpoGJyTILOVsDgdm7eutnLA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/client-sso@3.812.0': '@aws-sdk/client-sso@3.817.0':
resolution: {integrity: sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==} resolution: {integrity: sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/core@3.812.0': '@aws-sdk/core@3.816.0':
resolution: {integrity: sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==} resolution: {integrity: sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-env@3.812.0': '@aws-sdk/credential-provider-env@3.816.0':
resolution: {integrity: sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==} resolution: {integrity: sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-http@3.812.0': '@aws-sdk/credential-provider-http@3.816.0':
resolution: {integrity: sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==} resolution: {integrity: sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-ini@3.812.0': '@aws-sdk/credential-provider-ini@3.817.0':
resolution: {integrity: sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==} resolution: {integrity: sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-node@3.812.0': '@aws-sdk/credential-provider-node@3.817.0':
resolution: {integrity: sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==} resolution: {integrity: sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-process@3.812.0': '@aws-sdk/credential-provider-process@3.816.0':
resolution: {integrity: sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==} resolution: {integrity: sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-sso@3.812.0': '@aws-sdk/credential-provider-sso@3.817.0':
resolution: {integrity: sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==} resolution: {integrity: sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/credential-provider-web-identity@3.812.0': '@aws-sdk/credential-provider-web-identity@3.817.0':
resolution: {integrity: sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==} resolution: {integrity: sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/lib-storage@3.815.0': '@aws-sdk/lib-storage@3.817.0':
resolution: {integrity: sha512-67FgW0T/1UupfKYzASW/5JEvM3jq6aIEwHaJl56K4nBhwVBxr1lu/xVSZ80fA6lgysKHh5GAtQhueQCKwj53wA==} resolution: {integrity: sha512-2zOO8+2EmiS049PjLSNdqmmZMQj7fzE1hZJ70A94vO+KNaVhVZYuMOOiOmwMw6ePkTCcFwK40vZIIXwEQQ1v1g==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
peerDependencies: peerDependencies:
'@aws-sdk/client-s3': ^3.815.0 '@aws-sdk/client-s3': ^3.817.0
'@aws-sdk/middleware-bucket-endpoint@3.808.0': '@aws-sdk/middleware-bucket-endpoint@3.808.0':
resolution: {integrity: sha512-wEPlNcs8dir9lXbuviEGtSzYSxG/NRKQrJk5ybOc7OpPGHovsN+QhDOdY3lcjOFdwMTiMIG9foUkPz3zBpLB1A==} resolution: {integrity: sha512-wEPlNcs8dir9lXbuviEGtSzYSxG/NRKQrJk5ybOc7OpPGHovsN+QhDOdY3lcjOFdwMTiMIG9foUkPz3zBpLB1A==}
@ -1624,8 +1624,8 @@ packages:
resolution: {integrity: sha512-YW1hySBolALMII6C8y7Z0CRG2UX1dGJjLEBNFeefhO/xP7ZuE1dvnmfJGaEuBMnvc3wkRS63VZ3aqX6sevM1CA==} resolution: {integrity: sha512-YW1hySBolALMII6C8y7Z0CRG2UX1dGJjLEBNFeefhO/xP7ZuE1dvnmfJGaEuBMnvc3wkRS63VZ3aqX6sevM1CA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/middleware-flexible-checksums@3.815.0': '@aws-sdk/middleware-flexible-checksums@3.816.0':
resolution: {integrity: sha512-cv/BO7saBbHTrLMUJiClZHM/GB4xDBbJmZ70f9HwcNBP59tBB8TgF/vSyi8SdFM82TvRP+Zzi1AZ8hXcwElaCg==} resolution: {integrity: sha512-kftcwDxB/VoCBsUiRgkm5CIuKbTfCN1WLPbis9LRwX3kQhKgGVxG2gG78SHk4TBB0qviWVAd/t+i/KaUgwiAcA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/middleware-host-header@3.804.0': '@aws-sdk/middleware-host-header@3.804.0':
@ -1644,32 +1644,32 @@ packages:
resolution: {integrity: sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==} resolution: {integrity: sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/middleware-sdk-s3@3.812.0': '@aws-sdk/middleware-sdk-s3@3.816.0':
resolution: {integrity: sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==} resolution: {integrity: sha512-jJ+EAXM7gnOwiCM6rrl4AUNY5urmtIsX7roTkxtb4DevJxcS+wFYRRg3/j33fQbuxQZrvk21HqxyZYx5UH70PA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/middleware-ssec@3.804.0': '@aws-sdk/middleware-ssec@3.804.0':
resolution: {integrity: sha512-Tk8jK0gOIUBvEPTz/wwSlP1V70zVQ3QYqsLPAjQRMO6zfOK9ax31dln3MgKvFDJxBydS2tS3wsn53v+brxDxTA==} resolution: {integrity: sha512-Tk8jK0gOIUBvEPTz/wwSlP1V70zVQ3QYqsLPAjQRMO6zfOK9ax31dln3MgKvFDJxBydS2tS3wsn53v+brxDxTA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/middleware-user-agent@3.812.0': '@aws-sdk/middleware-user-agent@3.816.0':
resolution: {integrity: sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==} resolution: {integrity: sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/nested-clients@3.812.0': '@aws-sdk/nested-clients@3.817.0':
resolution: {integrity: sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==} resolution: {integrity: sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/region-config-resolver@3.808.0': '@aws-sdk/region-config-resolver@3.808.0':
resolution: {integrity: sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==} resolution: {integrity: sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/signature-v4-multi-region@3.812.0': '@aws-sdk/signature-v4-multi-region@3.816.0':
resolution: {integrity: sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==} resolution: {integrity: sha512-idcr9NW86sSIXASSej3423Selu6fxlhhJJtMgpAqoCH/HJh1eQrONJwNKuI9huiruPE8+02pwxuePvLW46X2mw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/token-providers@3.812.0': '@aws-sdk/token-providers@3.817.0':
resolution: {integrity: sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==} resolution: {integrity: sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@aws-sdk/types@3.804.0': '@aws-sdk/types@3.804.0':
@ -1691,8 +1691,8 @@ packages:
'@aws-sdk/util-user-agent-browser@3.804.0': '@aws-sdk/util-user-agent-browser@3.804.0':
resolution: {integrity: sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==} resolution: {integrity: sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==}
'@aws-sdk/util-user-agent-node@3.812.0': '@aws-sdk/util-user-agent-node@3.816.0':
resolution: {integrity: sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==} resolution: {integrity: sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
peerDependencies: peerDependencies:
aws-crt: '>=1.0.0' aws-crt: '>=1.0.0'
@ -2818,8 +2818,8 @@ packages:
resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==} resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
'@nestjs/common@11.1.1': '@nestjs/common@11.1.2':
resolution: {integrity: sha512-crzp+1qeZ5EGL0nFTPy9NrVMAaUWewV5AwtQyv6SQ9yQPXwRl9W9hm1pt0nAtUu5QbYMbSuo7lYcF81EjM+nCA==} resolution: {integrity: sha512-cHh4OPH44PjaHM93D1jgE1HO/B7XTZVRDxy/cPuGgyMEA4p2zXO+qqcOgTMC5FYcp7dX9jLeCjXAU0ToFAnODw==}
peerDependencies: peerDependencies:
class-transformer: '>=0.4.1' class-transformer: '>=0.4.1'
class-validator: '>=0.13.2' class-validator: '>=0.13.2'
@ -2831,8 +2831,8 @@ packages:
class-validator: class-validator:
optional: true optional: true
'@nestjs/core@11.1.1': '@nestjs/core@11.1.2':
resolution: {integrity: sha512-UFoUAgLKFT+RwHTANJdr0dF7p0qS9QjkaUPjg8aafnjM/qxxxrUVDB49nVvyMlk+Hr1+vvcNaOHbWWQBxoZcHA==} resolution: {integrity: sha512-QRuyxwu0BjNfmmmunsw1ylX7RSyfDQHt+xD+tKncdtgiMOOzAu+LA1gB4WoZnw4frQkk+qZbhEbM61cIjOxD3w==}
engines: {node: '>= 20'} engines: {node: '>= 20'}
peerDependencies: peerDependencies:
'@nestjs/common': ^11.0.0 '@nestjs/common': ^11.0.0
@ -2849,14 +2849,14 @@ packages:
'@nestjs/websockets': '@nestjs/websockets':
optional: true optional: true
'@nestjs/platform-express@10.4.17': '@nestjs/platform-express@10.4.18':
resolution: {integrity: sha512-ovn4Wxney3QGBrqNPv0QLcCuH5QoAi6pb/GNWAz6B/NmBjZbs9/zl4a2beGDA2SaYre9w43YbfmHTm17PneP9w==} resolution: {integrity: sha512-v+W+Pu5NOVK/bSG5A5mOnXyoVwN5mJUe4o0j0UJ9Ig9JMmjVxg+Zw2ydTfpOQ+R82lRYWJUjjv3dvqKaFW2z7w==}
peerDependencies: peerDependencies:
'@nestjs/common': ^10.0.0 '@nestjs/common': ^10.0.0
'@nestjs/core': ^10.0.0 '@nestjs/core': ^10.0.0
'@nestjs/testing@11.1.1': '@nestjs/testing@11.1.2':
resolution: {integrity: sha512-stzm8YrLDGAijHYQw+8Z9dD6lGdvahL0hIjGVZ/0KBxLZht0/rvRjgV31UK+DUqXaF7yhJTw9ryrPaITxI1J6A==} resolution: {integrity: sha512-BQxVKUVW6gzEbbHAvmg5RgcP3s++pRgTCmsgaDF/DtcLRUeKi8SjAdqzLm14xbkMeibxOf3fNqM2iwqUKj8ffw==}
peerDependencies: peerDependencies:
'@nestjs/common': ^11.0.0 '@nestjs/common': ^11.0.0
'@nestjs/core': ^11.0.0 '@nestjs/core': ^11.0.0
@ -6448,9 +6448,9 @@ packages:
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
file-type@20.5.0: file-type@21.0.0:
resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==}
engines: {node: '>=18'} engines: {node: '>=20'}
filename-reserved-regex@3.0.0: filename-reserved-regex@3.0.0:
resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
@ -6989,8 +6989,8 @@ packages:
inspect-with-kind@1.0.5: inspect-with-kind@1.0.5:
resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==}
install-artifact-from-github@1.3.5: install-artifact-from-github@1.4.0:
resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==} resolution: {integrity: sha512-+y6WywKZREw5rq7U2jvr2nmZpT7cbWbQQ0N/qfcseYnzHFz2cZz1Et52oY+XttYuYeTkI8Y+R2JNWj68MpQFSg==}
hasBin: true hasBin: true
internal-slot@1.0.7: internal-slot@1.0.7:
@ -8072,10 +8072,9 @@ packages:
muggle-string@0.4.1: muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
multer@1.4.4-lts.1: multer@2.0.0:
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} resolution: {integrity: sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==}
engines: {node: '>= 6.0.0'} engines: {node: '>= 10.16.0'}
deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.
mute-stream@2.0.0: mute-stream@2.0.0:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
@ -9094,8 +9093,8 @@ packages:
resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==} resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==}
engines: {node: '>=12'} engines: {node: '>=12'}
re2@1.21.5: re2@1.22.1:
resolution: {integrity: sha512-ud7gX1bO6K4+l2YVUxZjOPCiyCBZvmi7XUnGArSk3rGIvsZW35jX3pjGs8zQiTumOpgbxHCZI1ivB1VO7i4MFw==} resolution: {integrity: sha512-E4J0EtgyNLdIr0wTg0dQPefuiqNY29KaLacytiUAYYRzxCG+zOkWoUygt1rI+TA1LrhN49/njrfSO1DHtVC5Vw==}
react-docgen-typescript@2.2.2: react-docgen-typescript@2.2.2:
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
@ -9898,8 +9897,8 @@ packages:
symbol-tree@3.2.4: symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
systeminformation@5.26.1: systeminformation@5.27.1:
resolution: {integrity: sha512-Nd503zsVvWKBREk5ekpCqONR6EVeualuZNm1ZS2BBCX/f/AUsOmsF32UVHUj8CUghWQle+6MdelIxRENGDvGhA==} resolution: {integrity: sha512-FgkVpT6GgATtNvADgtEzDxI/SVaBisfnQ4fmgQZhCJ4335noTgt9q6O81ioHwzs9HgnJaaFSdHSEMIkneZ55iA==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true hasBin: true
@ -10527,6 +10526,9 @@ packages:
vue-component-type-helpers@2.2.10: vue-component-type-helpers@2.2.10:
resolution: {integrity: sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==} resolution: {integrity: sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==}
vue-component-type-helpers@3.0.0-alpha.8:
resolution: {integrity: sha512-90tDQ0Si5EOuBj1IWGg4o0prHt3H5xysZS/9z/m5TTC8xTKrDYAUQvbWiNVa9VXg/ED3ah6yDuQirXW+O6bTdA==}
vue-demi@0.14.7: vue-demi@0.14.7:
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -10919,29 +10921,29 @@ snapshots:
'@smithy/util-utf8': 2.0.0 '@smithy/util-utf8': 2.0.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/client-s3@3.815.0': '@aws-sdk/client-s3@3.817.0':
dependencies: dependencies:
'@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha1-browser': 5.2.0
'@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/credential-provider-node': 3.812.0 '@aws-sdk/credential-provider-node': 3.817.0
'@aws-sdk/middleware-bucket-endpoint': 3.808.0 '@aws-sdk/middleware-bucket-endpoint': 3.808.0
'@aws-sdk/middleware-expect-continue': 3.804.0 '@aws-sdk/middleware-expect-continue': 3.804.0
'@aws-sdk/middleware-flexible-checksums': 3.815.0 '@aws-sdk/middleware-flexible-checksums': 3.816.0
'@aws-sdk/middleware-host-header': 3.804.0 '@aws-sdk/middleware-host-header': 3.804.0
'@aws-sdk/middleware-location-constraint': 3.804.0 '@aws-sdk/middleware-location-constraint': 3.804.0
'@aws-sdk/middleware-logger': 3.804.0 '@aws-sdk/middleware-logger': 3.804.0
'@aws-sdk/middleware-recursion-detection': 3.804.0 '@aws-sdk/middleware-recursion-detection': 3.804.0
'@aws-sdk/middleware-sdk-s3': 3.812.0 '@aws-sdk/middleware-sdk-s3': 3.816.0
'@aws-sdk/middleware-ssec': 3.804.0 '@aws-sdk/middleware-ssec': 3.804.0
'@aws-sdk/middleware-user-agent': 3.812.0 '@aws-sdk/middleware-user-agent': 3.816.0
'@aws-sdk/region-config-resolver': 3.808.0 '@aws-sdk/region-config-resolver': 3.808.0
'@aws-sdk/signature-v4-multi-region': 3.812.0 '@aws-sdk/signature-v4-multi-region': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@aws-sdk/util-endpoints': 3.808.0 '@aws-sdk/util-endpoints': 3.808.0
'@aws-sdk/util-user-agent-browser': 3.804.0 '@aws-sdk/util-user-agent-browser': 3.804.0
'@aws-sdk/util-user-agent-node': 3.812.0 '@aws-sdk/util-user-agent-node': 3.816.0
'@aws-sdk/xml-builder': 3.804.0 '@aws-sdk/xml-builder': 3.804.0
'@smithy/config-resolver': 4.1.3 '@smithy/config-resolver': 4.1.3
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
@ -10980,20 +10982,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/client-sso@3.812.0': '@aws-sdk/client-sso@3.817.0':
dependencies: dependencies:
'@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/middleware-host-header': 3.804.0 '@aws-sdk/middleware-host-header': 3.804.0
'@aws-sdk/middleware-logger': 3.804.0 '@aws-sdk/middleware-logger': 3.804.0
'@aws-sdk/middleware-recursion-detection': 3.804.0 '@aws-sdk/middleware-recursion-detection': 3.804.0
'@aws-sdk/middleware-user-agent': 3.812.0 '@aws-sdk/middleware-user-agent': 3.816.0
'@aws-sdk/region-config-resolver': 3.808.0 '@aws-sdk/region-config-resolver': 3.808.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@aws-sdk/util-endpoints': 3.808.0 '@aws-sdk/util-endpoints': 3.808.0
'@aws-sdk/util-user-agent-browser': 3.804.0 '@aws-sdk/util-user-agent-browser': 3.804.0
'@aws-sdk/util-user-agent-node': 3.812.0 '@aws-sdk/util-user-agent-node': 3.816.0
'@smithy/config-resolver': 4.1.3 '@smithy/config-resolver': 4.1.3
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
'@smithy/fetch-http-handler': 5.0.3 '@smithy/fetch-http-handler': 5.0.3
@ -11023,7 +11025,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/core@3.812.0': '@aws-sdk/core@3.816.0':
dependencies: dependencies:
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
@ -11037,17 +11039,17 @@ snapshots:
fast-xml-parser: 4.4.1 fast-xml-parser: 4.4.1
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/credential-provider-env@3.812.0': '@aws-sdk/credential-provider-env@3.816.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/credential-provider-http@3.812.0': '@aws-sdk/credential-provider-http@3.816.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/fetch-http-handler': 5.0.3 '@smithy/fetch-http-handler': 5.0.3
'@smithy/node-http-handler': 4.0.5 '@smithy/node-http-handler': 4.0.5
@ -11058,15 +11060,15 @@ snapshots:
'@smithy/util-stream': 4.2.1 '@smithy/util-stream': 4.2.1
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/credential-provider-ini@3.812.0': '@aws-sdk/credential-provider-ini@3.817.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/credential-provider-env': 3.812.0 '@aws-sdk/credential-provider-env': 3.816.0
'@aws-sdk/credential-provider-http': 3.812.0 '@aws-sdk/credential-provider-http': 3.816.0
'@aws-sdk/credential-provider-process': 3.812.0 '@aws-sdk/credential-provider-process': 3.816.0
'@aws-sdk/credential-provider-sso': 3.812.0 '@aws-sdk/credential-provider-sso': 3.817.0
'@aws-sdk/credential-provider-web-identity': 3.812.0 '@aws-sdk/credential-provider-web-identity': 3.817.0
'@aws-sdk/nested-clients': 3.812.0 '@aws-sdk/nested-clients': 3.817.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/credential-provider-imds': 4.0.5 '@smithy/credential-provider-imds': 4.0.5
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
@ -11076,14 +11078,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/credential-provider-node@3.812.0': '@aws-sdk/credential-provider-node@3.817.0':
dependencies: dependencies:
'@aws-sdk/credential-provider-env': 3.812.0 '@aws-sdk/credential-provider-env': 3.816.0
'@aws-sdk/credential-provider-http': 3.812.0 '@aws-sdk/credential-provider-http': 3.816.0
'@aws-sdk/credential-provider-ini': 3.812.0 '@aws-sdk/credential-provider-ini': 3.817.0
'@aws-sdk/credential-provider-process': 3.812.0 '@aws-sdk/credential-provider-process': 3.816.0
'@aws-sdk/credential-provider-sso': 3.812.0 '@aws-sdk/credential-provider-sso': 3.817.0
'@aws-sdk/credential-provider-web-identity': 3.812.0 '@aws-sdk/credential-provider-web-identity': 3.817.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/credential-provider-imds': 4.0.5 '@smithy/credential-provider-imds': 4.0.5
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
@ -11093,20 +11095,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/credential-provider-process@3.812.0': '@aws-sdk/credential-provider-process@3.816.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
'@smithy/shared-ini-file-loader': 4.0.3 '@smithy/shared-ini-file-loader': 4.0.3
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/credential-provider-sso@3.812.0': '@aws-sdk/credential-provider-sso@3.817.0':
dependencies: dependencies:
'@aws-sdk/client-sso': 3.812.0 '@aws-sdk/client-sso': 3.817.0
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/token-providers': 3.812.0 '@aws-sdk/token-providers': 3.817.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
'@smithy/shared-ini-file-loader': 4.0.3 '@smithy/shared-ini-file-loader': 4.0.3
@ -11115,10 +11117,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/credential-provider-web-identity@3.812.0': '@aws-sdk/credential-provider-web-identity@3.817.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/nested-clients': 3.812.0 '@aws-sdk/nested-clients': 3.817.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
@ -11126,9 +11128,9 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@aws-sdk/lib-storage@3.815.0(@aws-sdk/client-s3@3.815.0)': '@aws-sdk/lib-storage@3.817.0(@aws-sdk/client-s3@3.817.0)':
dependencies: dependencies:
'@aws-sdk/client-s3': 3.815.0 '@aws-sdk/client-s3': 3.817.0
'@smithy/abort-controller': 4.0.3 '@smithy/abort-controller': 4.0.3
'@smithy/middleware-endpoint': 4.1.7 '@smithy/middleware-endpoint': 4.1.7
'@smithy/smithy-client': 4.3.0 '@smithy/smithy-client': 4.3.0
@ -11154,12 +11156,12 @@ snapshots:
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/middleware-flexible-checksums@3.815.0': '@aws-sdk/middleware-flexible-checksums@3.816.0':
dependencies: dependencies:
'@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32': 5.2.0
'@aws-crypto/crc32c': 5.2.0 '@aws-crypto/crc32c': 5.2.0
'@aws-crypto/util': 5.2.0 '@aws-crypto/util': 5.2.0
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/is-array-buffer': 4.0.0 '@smithy/is-array-buffer': 4.0.0
'@smithy/node-config-provider': 4.1.2 '@smithy/node-config-provider': 4.1.2
@ -11196,9 +11198,9 @@ snapshots:
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/middleware-sdk-s3@3.812.0': '@aws-sdk/middleware-sdk-s3@3.816.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@aws-sdk/util-arn-parser': 3.804.0 '@aws-sdk/util-arn-parser': 3.804.0
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
@ -11219,9 +11221,9 @@ snapshots:
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/middleware-user-agent@3.812.0': '@aws-sdk/middleware-user-agent@3.816.0':
dependencies: dependencies:
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@aws-sdk/util-endpoints': 3.808.0 '@aws-sdk/util-endpoints': 3.808.0
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
@ -11229,20 +11231,20 @@ snapshots:
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/nested-clients@3.812.0': '@aws-sdk/nested-clients@3.817.0':
dependencies: dependencies:
'@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/middleware-host-header': 3.804.0 '@aws-sdk/middleware-host-header': 3.804.0
'@aws-sdk/middleware-logger': 3.804.0 '@aws-sdk/middleware-logger': 3.804.0
'@aws-sdk/middleware-recursion-detection': 3.804.0 '@aws-sdk/middleware-recursion-detection': 3.804.0
'@aws-sdk/middleware-user-agent': 3.812.0 '@aws-sdk/middleware-user-agent': 3.816.0
'@aws-sdk/region-config-resolver': 3.808.0 '@aws-sdk/region-config-resolver': 3.808.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@aws-sdk/util-endpoints': 3.808.0 '@aws-sdk/util-endpoints': 3.808.0
'@aws-sdk/util-user-agent-browser': 3.804.0 '@aws-sdk/util-user-agent-browser': 3.804.0
'@aws-sdk/util-user-agent-node': 3.812.0 '@aws-sdk/util-user-agent-node': 3.816.0
'@smithy/config-resolver': 4.1.3 '@smithy/config-resolver': 4.1.3
'@smithy/core': 3.4.0 '@smithy/core': 3.4.0
'@smithy/fetch-http-handler': 5.0.3 '@smithy/fetch-http-handler': 5.0.3
@ -11281,18 +11283,19 @@ snapshots:
'@smithy/util-middleware': 4.0.3 '@smithy/util-middleware': 4.0.3
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/signature-v4-multi-region@3.812.0': '@aws-sdk/signature-v4-multi-region@3.816.0':
dependencies: dependencies:
'@aws-sdk/middleware-sdk-s3': 3.812.0 '@aws-sdk/middleware-sdk-s3': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/protocol-http': 5.1.1 '@smithy/protocol-http': 5.1.1
'@smithy/signature-v4': 5.1.0 '@smithy/signature-v4': 5.1.0
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/token-providers@3.812.0': '@aws-sdk/token-providers@3.817.0':
dependencies: dependencies:
'@aws-sdk/nested-clients': 3.812.0 '@aws-sdk/core': 3.816.0
'@aws-sdk/nested-clients': 3.817.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/property-provider': 4.0.3 '@smithy/property-provider': 4.0.3
'@smithy/shared-ini-file-loader': 4.0.3 '@smithy/shared-ini-file-loader': 4.0.3
@ -11328,9 +11331,9 @@ snapshots:
bowser: 2.11.0 bowser: 2.11.0
tslib: 2.8.1 tslib: 2.8.1
'@aws-sdk/util-user-agent-node@3.812.0': '@aws-sdk/util-user-agent-node@3.816.0':
dependencies: dependencies:
'@aws-sdk/middleware-user-agent': 3.812.0 '@aws-sdk/middleware-user-agent': 3.816.0
'@aws-sdk/types': 3.804.0 '@aws-sdk/types': 3.804.0
'@smithy/node-config-provider': 4.1.2 '@smithy/node-config-provider': 4.1.2
'@smithy/types': 4.3.0 '@smithy/types': 4.3.0
@ -11361,7 +11364,7 @@ snapshots:
'@babel/traverse': 7.24.7 '@babel/traverse': 7.24.7
'@babel/types': 7.25.6 '@babel/types': 7.25.6
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@ -11547,7 +11550,7 @@ snapshots:
'@babel/helper-split-export-declaration': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7
'@babel/parser': 7.25.6 '@babel/parser': 7.25.6
'@babel/types': 7.25.6 '@babel/types': 7.25.6
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -11810,7 +11813,7 @@ snapshots:
'@eslint/config-array@0.20.0': '@eslint/config-array@0.20.0':
dependencies: dependencies:
'@eslint/object-schema': 2.1.6 '@eslint/object-schema': 2.1.6
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
minimatch: 3.1.2 minimatch: 3.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -11824,7 +11827,7 @@ snapshots:
'@eslint/eslintrc@3.3.1': '@eslint/eslintrc@3.3.1':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
espree: 10.3.0 espree: 10.3.0
globals: 14.0.0 globals: 14.0.0
ignore: 5.3.1 ignore: 5.3.1
@ -12561,9 +12564,9 @@ snapshots:
'@napi-rs/canvas-linux-x64-musl': 0.1.70 '@napi-rs/canvas-linux-x64-musl': 0.1.70
'@napi-rs/canvas-win32-x64-msvc': 0.1.70 '@napi-rs/canvas-win32-x64-msvc': 0.1.70
'@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2)': '@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies: dependencies:
file-type: 20.5.0 file-type: 21.0.0
iterare: 1.2.1 iterare: 1.2.1
load-esm: 1.0.2 load-esm: 1.0.2
reflect-metadata: 0.2.2 reflect-metadata: 0.2.2
@ -12573,9 +12576,9 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@nestjs/core@11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)': '@nestjs/core@11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies: dependencies:
'@nestjs/common': 11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nuxt/opencollective': 0.4.1 '@nuxt/opencollective': 0.4.1
fast-safe-stringify: 2.1.1 fast-safe-stringify: 2.1.1
iterare: 1.2.1 iterare: 1.2.1
@ -12585,27 +12588,27 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
uid: 2.0.2 uid: 2.0.2
optionalDependencies: optionalDependencies:
'@nestjs/platform-express': 10.4.17(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1) '@nestjs/platform-express': 10.4.18(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)
'@nestjs/platform-express@10.4.17(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1)': '@nestjs/platform-express@10.4.18(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)':
dependencies: dependencies:
'@nestjs/common': 11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)
body-parser: 1.20.3 body-parser: 1.20.3
cors: 2.8.5 cors: 2.8.5
express: 4.21.2 express: 4.21.2
multer: 1.4.4-lts.1 multer: 2.0.0
tslib: 2.8.1 tslib: 2.8.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@nestjs/testing@11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1)(@nestjs/platform-express@10.4.17)': '@nestjs/testing@11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)(@nestjs/platform-express@10.4.18)':
dependencies: dependencies:
'@nestjs/common': 11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.1(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.2(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
'@nestjs/platform-express': 10.4.17(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1) '@nestjs/platform-express': 10.4.18(@nestjs/common@11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.2)
'@noble/hashes@1.7.1': {} '@noble/hashes@1.7.1': {}
@ -14042,7 +14045,7 @@ snapshots:
ts-dedent: 2.2.0 ts-dedent: 2.2.0
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.5.14(typescript@5.8.3) vue: 3.5.14(typescript@5.8.3)
vue-component-type-helpers: 2.2.10 vue-component-type-helpers: 3.0.0-alpha.8
'@stylistic/eslint-plugin@2.13.0(eslint@9.27.0)(typescript@5.8.3)': '@stylistic/eslint-plugin@2.13.0(eslint@9.27.0)(typescript@5.8.3)':
dependencies: dependencies:
@ -14286,7 +14289,7 @@ snapshots:
'@tokenizer/inflate@0.2.7': '@tokenizer/inflate@0.2.7':
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
fflate: 0.8.2 fflate: 0.8.2
token-types: 6.0.0 token-types: 6.0.0
transitivePeerDependencies: transitivePeerDependencies:
@ -14672,7 +14675,7 @@ snapshots:
'@typescript-eslint/types': 8.32.1 '@typescript-eslint/types': 8.32.1
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.32.1 '@typescript-eslint/visitor-keys': 8.32.1
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
eslint: 9.27.0 eslint: 9.27.0
typescript: 5.8.3 typescript: 5.8.3
transitivePeerDependencies: transitivePeerDependencies:
@ -14687,7 +14690,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3) '@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
eslint: 9.27.0 eslint: 9.27.0
ts-api-utils: 2.1.0(typescript@5.8.3) ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3 typescript: 5.8.3
@ -14700,7 +14703,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/types': 8.32.1 '@typescript-eslint/types': 8.32.1
'@typescript-eslint/visitor-keys': 8.32.1 '@typescript-eslint/visitor-keys': 8.32.1
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
fast-glob: 3.3.3 fast-glob: 3.3.3
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.5 minimatch: 9.0.5
@ -14737,7 +14740,7 @@ snapshots:
dependencies: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2 '@bcoe/v8-coverage': 1.0.2
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.2 istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1 istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6 istanbul-lib-source-maps: 5.0.6
@ -15063,7 +15066,7 @@ snapshots:
agent-base@6.0.2: agent-base@6.0.2:
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
optional: true optional: true
@ -16554,7 +16557,7 @@ snapshots:
esbuild-register@3.5.0(esbuild@0.25.4): esbuild-register@3.5.0(esbuild@0.25.4):
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
esbuild: 0.25.4 esbuild: 0.25.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -16701,7 +16704,7 @@ snapshots:
ajv: 6.12.6 ajv: 6.12.6
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 8.3.0 eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0 eslint-visitor-keys: 4.2.0
@ -17038,7 +17041,7 @@ snapshots:
token-types: 6.0.0 token-types: 6.0.0
uint8array-extras: 1.4.0 uint8array-extras: 1.4.0
file-type@20.5.0: file-type@21.0.0:
dependencies: dependencies:
'@tokenizer/inflate': 0.2.7 '@tokenizer/inflate': 0.2.7
strtok3: 10.2.2 strtok3: 10.2.2
@ -17114,7 +17117,7 @@ snapshots:
follow-redirects@1.15.9(debug@4.4.1): follow-redirects@1.15.9(debug@4.4.1):
optionalDependencies: optionalDependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
for-each@0.3.3: for-each@0.3.3:
dependencies: dependencies:
@ -17512,7 +17515,7 @@ snapshots:
http-proxy-agent@7.0.2: http-proxy-agent@7.0.2:
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -17540,7 +17543,7 @@ snapshots:
https-proxy-agent@5.0.1: https-proxy-agent@5.0.1:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
optional: true optional: true
@ -17548,7 +17551,7 @@ snapshots:
https-proxy-agent@7.0.6: https-proxy-agent@7.0.6:
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -17628,7 +17631,7 @@ snapshots:
dependencies: dependencies:
kind-of: 6.0.3 kind-of: 6.0.3
install-artifact-from-github@1.3.5: {} install-artifact-from-github@1.4.0: {}
internal-slot@1.0.7: internal-slot@1.0.7:
dependencies: dependencies:
@ -17642,7 +17645,7 @@ snapshots:
dependencies: dependencies:
'@ioredis/commands': 1.2.0 '@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2 cluster-key-slot: 1.1.2
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
denque: 2.1.0 denque: 2.1.0
lodash.defaults: 4.2.0 lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0 lodash.isarguments: 3.1.0
@ -17852,7 +17855,7 @@ snapshots:
istanbul-lib-source-maps@4.0.1: istanbul-lib-source-maps@4.0.1:
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.2 istanbul-lib-coverage: 3.2.2
source-map: 0.6.1 source-map: 0.6.1
transitivePeerDependencies: transitivePeerDependencies:
@ -17861,7 +17864,7 @@ snapshots:
istanbul-lib-source-maps@5.0.6: istanbul-lib-source-maps@5.0.6:
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.2 istanbul-lib-coverage: 3.2.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -18877,7 +18880,7 @@ snapshots:
micromark@4.0.0: micromark@4.0.0:
dependencies: dependencies:
'@types/debug': 4.1.12 '@types/debug': 4.1.12
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
decode-named-character-reference: 1.0.2 decode-named-character-reference: 1.0.2
devlop: 1.1.0 devlop: 1.1.0
micromark-core-commonmark: 2.0.0 micromark-core-commonmark: 2.0.0
@ -19079,7 +19082,7 @@ snapshots:
muggle-string@0.4.1: {} muggle-string@0.4.1: {}
multer@1.4.4-lts.1: multer@2.0.0:
dependencies: dependencies:
append-field: 1.0.0 append-field: 1.0.0
busboy: 1.6.0 busboy: 1.6.0
@ -20092,9 +20095,9 @@ snapshots:
dependencies: dependencies:
setimmediate: 1.0.5 setimmediate: 1.0.5
re2@1.21.5: re2@1.22.1:
dependencies: dependencies:
install-artifact-from-github: 1.3.5 install-artifact-from-github: 1.4.0
nan: 2.22.2 nan: 2.22.2
node-gyp: 11.2.0 node-gyp: 11.2.0
transitivePeerDependencies: transitivePeerDependencies:
@ -20266,7 +20269,7 @@ snapshots:
require-in-the-middle@7.3.0: require-in-the-middle@7.3.0:
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
module-details-from-path: 1.0.3 module-details-from-path: 1.0.3
resolve: 1.22.8 resolve: 1.22.8
transitivePeerDependencies: transitivePeerDependencies:
@ -20623,7 +20626,7 @@ snapshots:
dependencies: dependencies:
'@hapi/hoek': 11.0.4 '@hapi/hoek': 11.0.4
'@hapi/wreck': 18.0.1 '@hapi/wreck': 18.0.1
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
joi: 17.13.3 joi: 17.13.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -20723,7 +20726,7 @@ snapshots:
socks-proxy-agent@8.0.5: socks-proxy-agent@8.0.5:
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
socks: 2.8.4 socks: 2.8.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -20820,7 +20823,7 @@ snapshots:
arg: 5.0.2 arg: 5.0.2
bluebird: 3.7.2 bluebird: 3.7.2
check-more-types: 2.24.0 check-more-types: 2.24.0
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
execa: 5.1.1 execa: 5.1.1
lazy-ass: 1.6.0 lazy-ass: 1.6.0
ps-tree: 1.2.0 ps-tree: 1.2.0
@ -20998,7 +21001,7 @@ snapshots:
dependencies: dependencies:
component-emitter: 1.3.1 component-emitter: 1.3.1
cookiejar: 2.1.4 cookiejar: 2.1.4
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
fast-safe-stringify: 2.1.1 fast-safe-stringify: 2.1.1
form-data: 4.0.2 form-data: 4.0.2
formidable: 3.5.4 formidable: 3.5.4
@ -21048,7 +21051,7 @@ snapshots:
symbol-tree@3.2.4: {} symbol-tree@3.2.4: {}
systeminformation@5.26.1: {} systeminformation@5.27.1: {}
tar-fs@2.1.2: tar-fs@2.1.2:
dependencies: dependencies:
@ -21343,7 +21346,7 @@ snapshots:
app-root-path: 3.1.0 app-root-path: 3.1.0
buffer: 6.0.3 buffer: 6.0.3
dayjs: 1.11.13 dayjs: 1.11.13
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
dedent: 1.6.0 dedent: 1.6.0
dotenv: 16.4.7 dotenv: 16.4.7
glob: 10.4.5 glob: 10.4.5
@ -21539,7 +21542,7 @@ snapshots:
vite-node@3.1.4(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4): vite-node@3.1.4(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
es-module-lexer: 1.7.0 es-module-lexer: 1.7.0
pathe: 2.0.3 pathe: 2.0.3
vite: 6.3.5(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4) vite: 6.3.5(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4)
@ -21588,7 +21591,7 @@ snapshots:
'@vitest/spy': 3.1.4 '@vitest/spy': 3.1.4
'@vitest/utils': 3.1.4 '@vitest/utils': 3.1.4
chai: 5.2.0 chai: 5.2.0
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
expect-type: 1.2.1 expect-type: 1.2.1
magic-string: 0.30.17 magic-string: 0.30.17
pathe: 2.0.3 pathe: 2.0.3
@ -21654,6 +21657,8 @@ snapshots:
vue-component-type-helpers@2.2.10: {} vue-component-type-helpers@2.2.10: {}
vue-component-type-helpers@3.0.0-alpha.8: {}
vue-demi@0.14.7(vue@3.5.14(typescript@5.8.3)): vue-demi@0.14.7(vue@3.5.14(typescript@5.8.3)):
dependencies: dependencies:
vue: 3.5.14(typescript@5.8.3) vue: 3.5.14(typescript@5.8.3)
@ -21675,7 +21680,7 @@ snapshots:
vue-eslint-parser@10.1.3(eslint@9.27.0): vue-eslint-parser@10.1.3(eslint@9.27.0):
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@5.5.0)
eslint: 9.27.0 eslint: 9.27.0
eslint-scope: 8.3.0 eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0 eslint-visitor-keys: 4.2.0