Compare commits
19 Commits
de90b606c1
...
9fb0f7357a
| Author | SHA1 | Date |
|---|---|---|
|
|
9fb0f7357a | |
|
|
bd8d0d78bf | |
|
|
31c4237748 | |
|
|
fad3aed79e | |
|
|
26819689bd | |
|
|
a8cbbdff63 | |
|
|
09eb631fdc | |
|
|
33486ebdf2 | |
|
|
092048e2e9 | |
|
|
0a4ca368d4 | |
|
|
eea0fb2636 | |
|
|
9bbc2028ad | |
|
|
97e916c912 | |
|
|
e954060f3b | |
|
|
e078cd9296 | |
|
|
1276e65049 | |
|
|
07c2de3749 | |
|
|
47f4f11e3e | |
|
|
d27075c5f5 |
|
|
@ -16,6 +16,7 @@
|
|||
- デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。
|
||||
- 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。
|
||||
- したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。
|
||||
- Feat: プレビュー先がリダイレクトを伴う場合、リダイレクト先のコンテンツを取得しに行くか否かを設定できるように(#16043)
|
||||
- Enhance: UIのアイコンデータの読み込みを軽量化
|
||||
|
||||
### Client
|
||||
|
|
|
|||
|
|
@ -581,27 +581,6 @@ pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
|
|||
- 生成後、ファイルを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`せよ
|
||||
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。
|
||||
|
||||
|
|
|
|||
|
|
@ -5473,6 +5473,10 @@ export interface Locale extends ILocale {
|
|||
* 全ての「ヒントとコツ」を非表示
|
||||
*/
|
||||
"hideAllTips": string;
|
||||
/**
|
||||
* デフォルトの画像圧縮度
|
||||
*/
|
||||
"defaultImageCompressionLevel": string;
|
||||
"_chat": {
|
||||
/**
|
||||
* まだメッセージはありません
|
||||
|
|
@ -11232,6 +11236,14 @@ export interface Locale extends ILocale {
|
|||
* URLプレビューを有効にする
|
||||
*/
|
||||
"enable": string;
|
||||
/**
|
||||
* プレビュー先のリダイレクトを許可
|
||||
*/
|
||||
"allowRedirect": string;
|
||||
/**
|
||||
* 入力されたURLがリダイレクトされる場合に、そのリダイレクト先をたどってプレビューを表示するかどうかを設定します。無効にするとサーバーリソースの節約になりますが、リダイレクト先の内容は表示されなくなります。
|
||||
*/
|
||||
"allowRedirectDescription": string;
|
||||
/**
|
||||
* プレビュー取得時のタイムアウト(ms)
|
||||
*/
|
||||
|
|
@ -12008,6 +12020,10 @@ export interface Locale extends ILocale {
|
|||
* ウォーターマーク
|
||||
*/
|
||||
"watermark": string;
|
||||
/**
|
||||
* デフォルトのプリセット
|
||||
*/
|
||||
"defaultPreset": string;
|
||||
"_watermarkEditor": {
|
||||
/**
|
||||
* 画像にクレジット情報などのウォーターマークを追加することができます。
|
||||
|
|
@ -12017,6 +12033,10 @@ export interface Locale extends ILocale {
|
|||
* ウォーターマークの編集
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* 全体に被せる
|
||||
*/
|
||||
"cover": string;
|
||||
/**
|
||||
* 敷き詰める
|
||||
*/
|
||||
|
|
@ -12046,6 +12066,12 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"image": string;
|
||||
};
|
||||
"_imageEffector": {
|
||||
/**
|
||||
* エフェクト
|
||||
*/
|
||||
"title": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
[lang: string]: Locale;
|
||||
|
|
|
|||
|
|
@ -1363,6 +1363,7 @@ abort: "中止"
|
|||
tip: "ヒントとコツ"
|
||||
redisplayAllTips: "全ての「ヒントとコツ」を再表示"
|
||||
hideAllTips: "全ての「ヒントとコツ」を非表示"
|
||||
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
|
||||
|
||||
_chat:
|
||||
noMessagesYet: "まだメッセージはありません"
|
||||
|
|
@ -2987,6 +2988,8 @@ _offlineScreen:
|
|||
_urlPreviewSetting:
|
||||
title: "URLプレビューの設定"
|
||||
enable: "URLプレビューを有効にする"
|
||||
allowRedirect: "プレビュー先のリダイレクトを許可"
|
||||
allowRedirectDescription: "入力されたURLがリダイレクトされる場合に、そのリダイレクト先をたどってプレビューを表示するかどうかを設定します。無効にするとサーバーリソースの節約になりますが、リダイレクト先の内容は表示されなくなります。"
|
||||
timeout: "プレビュー取得時のタイムアウト(ms)"
|
||||
timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
|
||||
maximumContentLength: "Content-Lengthの最大値(byte)"
|
||||
|
|
@ -3216,9 +3219,11 @@ _userLists:
|
|||
tip: "任意のユーザーが含まれるリストを作成できます。作成したリストはタイムラインとして表示可能です。"
|
||||
|
||||
watermark: "ウォーターマーク"
|
||||
defaultPreset: "デフォルトのプリセット"
|
||||
_watermarkEditor:
|
||||
tip: "画像にクレジット情報などのウォーターマークを追加することができます。"
|
||||
title: "ウォーターマークの編集"
|
||||
cover: "全体に被せる"
|
||||
repeat: "敷き詰める"
|
||||
opacity: "不透明度"
|
||||
scale: "サイズ"
|
||||
|
|
@ -3226,3 +3231,6 @@ _watermarkEditor:
|
|||
position: "位置"
|
||||
type: "タイプ"
|
||||
image: "画像"
|
||||
|
||||
_imageEffector:
|
||||
title: "エフェクト"
|
||||
|
|
|
|||
|
|
@ -17,4 +17,15 @@ args.push(...[
|
|||
...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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -67,8 +67,8 @@
|
|||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.815.0",
|
||||
"@aws-sdk/lib-storage": "3.815.0",
|
||||
"@aws-sdk/client-s3": "3.817.0",
|
||||
"@aws-sdk/lib-storage": "3.817.0",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
|
|
@ -81,9 +81,9 @@
|
|||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.1",
|
||||
"@napi-rs/canvas": "0.1.70",
|
||||
"@nestjs/common": "11.1.1",
|
||||
"@nestjs/core": "11.1.1",
|
||||
"@nestjs/testing": "11.1.1",
|
||||
"@nestjs/common": "11.1.2",
|
||||
"@nestjs/core": "11.1.2",
|
||||
"@nestjs/testing": "11.1.2",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "8.55.0",
|
||||
"@sentry/profiling-node": "8.55.0",
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
"qrcode": "1.5.4",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.21.5",
|
||||
"re2": "1.22.1",
|
||||
"redis-info": "3.1.0",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.2.2",
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.26.1",
|
||||
"systeminformation": "5.27.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.3",
|
||||
"tsc-alias": "1.8.16",
|
||||
|
|
@ -188,7 +188,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@nestjs/platform-express": "10.4.17",
|
||||
"@nestjs/platform-express": "10.4.18",
|
||||
"@sentry/vue": "9.22.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.38",
|
||||
|
|
|
|||
|
|
@ -218,7 +218,17 @@ type NullOrUndefined<p extends Schema, T> =
|
|||
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
|
||||
// Get intersection from union
|
||||
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
|
||||
// To get union, we use `Foo extends any ? Hoge<Foo> : never`
|
||||
|
|
@ -236,8 +246,8 @@ type ObjectSchemaTypeDef<p extends Schema> =
|
|||
: never
|
||||
: ObjType<p['properties'], NonNullable<p['required']>>
|
||||
:
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
|
||||
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> :
|
||||
p['allOf'] extends ReadonlyArray<Schema> ? ArrayToIntersection<p['allOf']> :
|
||||
p['additionalProperties'] extends true ? Record<string, any> :
|
||||
p['additionalProperties'] extends Schema ?
|
||||
p['additionalProperties'] extends infer AdditionalProperties ?
|
||||
|
|
@ -277,7 +287,8 @@ export type SchemaTypeDef<p extends Schema> =
|
|||
p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
|
||||
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']> :
|
||||
any;
|
||||
|
||||
|
|
|
|||
|
|
@ -619,6 +619,11 @@ export class MiMeta {
|
|||
})
|
||||
public urlPreviewEnabled: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public urlPreviewAllowRedirect: boolean;
|
||||
|
||||
@Column('integer', {
|
||||
default: 10000,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -162,14 +162,21 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
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;
|
||||
|
||||
|
|
@ -186,15 +193,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({
|
||||
where: [{
|
||||
url: ps.url,
|
||||
}, {
|
||||
thumbnailUrl: ps.url,
|
||||
}, {
|
||||
webpublicUrl: ps.url,
|
||||
}],
|
||||
});
|
||||
const file = await this.driveFilesRepository.findOneBy(
|
||||
'fileId' in ps
|
||||
? { id: ps.fileId }
|
||||
: [{ url: ps.url }, { thumbnailUrl: ps.url }, { webpublicUrl: ps.url }],
|
||||
);
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
|
|
|
|||
|
|
@ -37,29 +37,45 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
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;
|
||||
|
||||
|
|
@ -78,10 +94,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
// JSON schemeのanyOfの型変換がうまくいっていないらしい
|
||||
const required = { id: ps.id, name: ps.name } as
|
||||
| { id: MiEmoji['id']; name?: string }
|
||||
| { id?: MiEmoji['id']; name: string };
|
||||
const required = 'id' in ps
|
||||
? { id: ps.id, name: 'name' in ps ? ps.name as string : undefined }
|
||||
: { name: ps.name };
|
||||
|
||||
const error = await this.customEmojiService.update({
|
||||
...required,
|
||||
|
|
|
|||
|
|
@ -495,6 +495,10 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
urlPreviewAllowRedirect: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
urlPreviewTimeout: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
|
|
@ -704,6 +708,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
notesPerOneAd: instance.notesPerOneAd,
|
||||
summalyProxy: instance.urlPreviewSummaryProxyUrl,
|
||||
urlPreviewEnabled: instance.urlPreviewEnabled,
|
||||
urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect,
|
||||
urlPreviewTimeout: instance.urlPreviewTimeout,
|
||||
urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
|
||||
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ export const paramDef = {
|
|||
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
|
||||
},
|
||||
urlPreviewEnabled: { type: 'boolean' },
|
||||
urlPreviewAllowRedirect: { type: 'boolean' },
|
||||
urlPreviewTimeout: { type: 'integer' },
|
||||
urlPreviewMaximumContentLength: { type: 'integer' },
|
||||
urlPreviewRequireContentLength: { type: 'boolean' },
|
||||
|
|
@ -664,6 +665,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.urlPreviewEnabled = ps.urlPreviewEnabled;
|
||||
}
|
||||
|
||||
if (ps.urlPreviewAllowRedirect !== undefined) {
|
||||
set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect;
|
||||
}
|
||||
|
||||
if (ps.urlPreviewTimeout !== undefined) {
|
||||
set.urlPreviewTimeout = ps.urlPreviewTimeout;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,14 +43,21 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
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;
|
||||
|
||||
|
|
@ -64,21 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let file: MiDriveFile | null = null;
|
||||
|
||||
if (ps.fileId) {
|
||||
file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
} else if (ps.url) {
|
||||
file = await this.driveFilesRepository.findOne({
|
||||
where: [{
|
||||
url: ps.url,
|
||||
}, {
|
||||
webpublicUrl: ps.url,
|
||||
}, {
|
||||
thumbnailUrl: ps.url,
|
||||
}],
|
||||
});
|
||||
}
|
||||
const file = await this.driveFilesRepository.findOneBy(
|
||||
'fileId' in ps
|
||||
? { id: ps.fileId }
|
||||
: [{ url: ps.url }, { webpublicUrl: ps.url }, { thumbnailUrl: ps.url }],
|
||||
);
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
|
|
|
|||
|
|
@ -15,14 +15,21 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tokenId: { type: 'string', format: 'misskey:id' },
|
||||
token: { type: 'string', nullable: true },
|
||||
},
|
||||
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;
|
||||
|
||||
|
|
@ -33,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private accessTokensRepository: AccessTokensRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (ps.tokenId) {
|
||||
if ('tokenId' in ps) {
|
||||
const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
|
||||
|
||||
if (tokenExist) {
|
||||
|
|
|
|||
|
|
@ -28,38 +28,53 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
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 },
|
||||
|
||||
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,
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string', minLength: 1 },
|
||||
},
|
||||
required: ['tag'],
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
minItems: 1,
|
||||
{
|
||||
type: 'object',
|
||||
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;
|
||||
|
||||
|
|
@ -87,12 +102,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
|
||||
try {
|
||||
if (ps.tag) {
|
||||
if ('tag' in ps) {
|
||||
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
|
||||
query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
|
||||
} else {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
for (const tags of ps.query!) {
|
||||
for (const tags of ps.query) {
|
||||
qb.orWhere(new Brackets(qb => {
|
||||
for (const tag of tags) {
|
||||
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
|
||||
|
|
|
|||
|
|
@ -33,15 +33,22 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pageId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
},
|
||||
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;
|
||||
|
||||
|
|
@ -59,9 +66,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
super(meta, paramDef, async (ps, me) => {
|
||||
let page: MiPage | null = null;
|
||||
|
||||
if (ps.pageId) {
|
||||
if ('pageId' in ps) {
|
||||
page = await this.pagesRepository.findOneBy({ id: ps.pageId });
|
||||
} else if (ps.name && ps.username) {
|
||||
} else {
|
||||
const author = await this.usersRepository.findOneBy({
|
||||
host: IsNull(),
|
||||
usernameLower: ps.username.toLowerCase(),
|
||||
|
|
|
|||
|
|
@ -47,23 +47,38 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -85,9 +100,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private roleService: RoleService,
|
||||
) {
|
||||
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 }
|
||||
: { 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) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
|
|
|
|||
|
|
@ -54,25 +54,39 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -94,9 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private roleService: RoleService,
|
||||
) {
|
||||
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 }
|
||||
: { 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) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export const paramDef = {
|
|||
type: 'object',
|
||||
properties: {
|
||||
userId: {
|
||||
anyOf: [
|
||||
oneOf: [
|
||||
{ type: 'string', format: 'misskey:id' },
|
||||
{
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -26,17 +26,32 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
detail: { type: 'boolean', default: true },
|
||||
|
||||
username: { type: 'string', nullable: true },
|
||||
host: { type: 'string', nullable: true },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['username'] },
|
||||
{ required: ['host'] },
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['username'],
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -47,8 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
) {
|
||||
super(meta, paramDef, (ps, me) => {
|
||||
return this.userSearchService.searchByUsernameAndHost({
|
||||
username: ps.username,
|
||||
host: ps.host,
|
||||
username: 'username' in ps ? ps.username : undefined,
|
||||
host: 'host' in ps ? ps.host : undefined,
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
detail: ps.detail,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
@ -59,23 +59,44 @@ export const meta = {
|
|||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
userIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -102,9 +123,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
let user;
|
||||
|
||||
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) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -129,7 +152,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return _users.map(u => _userMap.get(u.id)!);
|
||||
} else {
|
||||
// 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) {
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
const q: FindOptionsWhere<MiUser> = ps.userId != null
|
||||
const q: FindOptionsWhere<MiUser> = 'userId' in ps
|
||||
? { id: ps.userId }
|
||||
: { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,8 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
|||
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 = {
|
||||
operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export class UrlPreviewService {
|
|||
: undefined;
|
||||
|
||||
return summaly(url, {
|
||||
followRedirects: false,
|
||||
followRedirects: this.meta.urlPreviewAllowRedirect,
|
||||
lang: lang ?? 'ja-JP',
|
||||
agent: agent,
|
||||
userAgent: meta.urlPreviewUserAgent ?? undefined,
|
||||
|
|
@ -137,6 +137,7 @@ export class UrlPreviewService {
|
|||
const queryStr = query({
|
||||
url: url,
|
||||
lang: lang ?? 'ja-JP',
|
||||
followRedirects: this.meta.urlPreviewAllowRedirect,
|
||||
userAgent: meta.urlPreviewUserAgent ?? undefined,
|
||||
operationTimeout: meta.urlPreviewTimeout,
|
||||
contentLengthLimit: meta.urlPreviewMaximumContentLength,
|
||||
|
|
|
|||
|
|
@ -162,10 +162,10 @@ describe('AbuseReportNotificationService', () => {
|
|||
emailService.sendEmail.mockClear();
|
||||
webhookService.enqueueSystemWebhook.mockClear();
|
||||
|
||||
await usersRepository.delete({});
|
||||
await userProfilesRepository.delete({});
|
||||
await systemWebhooksRepository.delete({});
|
||||
await abuseReportNotificationRecipientRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||
await systemWebhooksRepository.createQueryBuilder().delete().execute();
|
||||
await abuseReportNotificationRecipientRepository.createQueryBuilder().delete().execute();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ describe('AnnouncementService', () => {
|
|||
|
||||
afterEach(async () => {
|
||||
await Promise.all([
|
||||
app.get(DI.metasRepository).delete({}),
|
||||
usersRepository.delete({}),
|
||||
announcementsRepository.delete({}),
|
||||
announcementReadsRepository.delete({}),
|
||||
app.get(DI.metasRepository).createQueryBuilder().delete().execute(),
|
||||
usersRepository.createQueryBuilder().delete().execute(),
|
||||
announcementsRepository.createQueryBuilder().delete().execute(),
|
||||
announcementReadsRepository.createQueryBuilder().delete().execute(),
|
||||
]);
|
||||
|
||||
await app.close();
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ describe('CustomEmojiService', () => {
|
|||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await emojisRepository.delete({});
|
||||
await emojisRepository.createQueryBuilder().delete().execute();
|
||||
});
|
||||
|
||||
describe('単独', () => {
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ describe('FlashService', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await usersRepository.delete({});
|
||||
await userProfilesRepository.delete({});
|
||||
await flashsRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||
await flashsRepository.createQueryBuilder().delete().execute();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
|
|
@ -159,10 +159,10 @@ describe('RoleService', () => {
|
|||
clock.uninstall();
|
||||
|
||||
await Promise.all([
|
||||
app.get(DI.metasRepository).delete({}),
|
||||
usersRepository.delete({}),
|
||||
rolesRepository.delete({}),
|
||||
roleAssignmentsRepository.delete({}),
|
||||
app.get(DI.metasRepository).createQueryBuilder().delete().execute(),
|
||||
usersRepository.createQueryBuilder().delete().execute(),
|
||||
rolesRepository.createQueryBuilder().delete().execute(),
|
||||
roleAssignmentsRepository.createQueryBuilder().delete().execute(),
|
||||
]);
|
||||
|
||||
await app.close();
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ describe('SystemWebhookService', () => {
|
|||
}
|
||||
|
||||
async function afterEachImpl() {
|
||||
await usersRepository.delete({});
|
||||
await systemWebhooksRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await systemWebhooksRepository.createQueryBuilder().delete().execute();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ describe('UserSearchService', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await usersRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ describe('UserWebhookService', () => {
|
|||
}
|
||||
|
||||
async function afterEachImpl() {
|
||||
await usersRepository.delete({});
|
||||
await userWebhooksRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await userWebhooksRepository.createQueryBuilder().delete().execute();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@ describe('WebhookTestService', () => {
|
|||
userWebhookService.fetchWebhooks.mockClear();
|
||||
systemWebhookService.fetchSystemWebhooks.mockClear();
|
||||
|
||||
await usersRepository.delete({});
|
||||
await userProfilesRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ describe('CheckModeratorsActivityProcessorService', () => {
|
|||
|
||||
afterEach(async () => {
|
||||
clock.uninstall();
|
||||
await usersRepository.delete({});
|
||||
await userProfilesRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||
roleService.getModerators.mockReset();
|
||||
announcementService.create.mockReset();
|
||||
emailService.sendEmail.mockReset();
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe('/drive/files/create', () => {
|
|||
idService = module.get(IdService);
|
||||
|
||||
const usersRepository = module.get<UsersRepository>(DI.usersRepository);
|
||||
await usersRepository.delete({});
|
||||
await usersRepository.createQueryBuilder().delete().execute();
|
||||
root = await usersRepository.insert({
|
||||
id: idService.gen(),
|
||||
username: 'root',
|
||||
|
|
@ -50,7 +50,7 @@ describe('/drive/files/create', () => {
|
|||
}).then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
const userProfilesRepository = module.get<UserProfilesRepository>(DI.userProfilesRepository);
|
||||
await userProfilesRepository.delete({});
|
||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||
await userProfilesRepository.insert({
|
||||
userId: root.id,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,9 +66,15 @@ export function getConfig(): UserConfig {
|
|||
return {
|
||||
base: '/embed_vite/',
|
||||
|
||||
// The console is shared with backend, so clearing the console will also clear the backend log.
|
||||
clearScreen: false,
|
||||
|
||||
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,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
// バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される
|
||||
// そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -40,8 +40,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div><MkCondensedLine :minScale="2 / 3">{{ ctx.name }}</MkCondensedLine></div>
|
||||
<div :class="$style.itemInfo">
|
||||
<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-else>{{ bytes(ctx.file.size) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
|
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<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 { v4 as uuid } from 'uuid';
|
||||
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 * as os from '@/os.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();
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ const items = ref<{
|
|||
uploaded: Misskey.entities.DriveFile | null;
|
||||
uploadFailed: boolean;
|
||||
aborted: boolean;
|
||||
compressionLevel: 0 | 1 | 2 | 3;
|
||||
compressionLevel: number;
|
||||
compressedSize?: number | null;
|
||||
preprocessedFile?: Blob | null;
|
||||
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) {
|
||||
function changeWatermarkPreset(presetId: string | null) {
|
||||
item.watermarkPresetId = presetId;
|
||||
|
|
@ -362,6 +380,7 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
|||
icon: 'ti ti-x',
|
||||
text: i18n.ts.remove,
|
||||
action: () => {
|
||||
URL.revokeObjectURL(item.thumbnail);
|
||||
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);
|
||||
if (needsWatermark && preset != null) {
|
||||
const canvas = window.document.createElement('canvas');
|
||||
const renderer = new Watermarker({
|
||||
const renderer = new ImageEffector({
|
||||
canvas: canvas,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
preset: preset,
|
||||
layers: makeImageEffectorLayers(preset.layers),
|
||||
originalImage: img,
|
||||
});
|
||||
|
||||
|
|
@ -557,8 +576,8 @@ function initializeFile(file: File) {
|
|||
aborted: false,
|
||||
uploaded: null,
|
||||
uploadFailed: false,
|
||||
compressionLevel: 2 as 0 | 1 | 2 | 3,
|
||||
watermarkPresetId: null,
|
||||
compressionLevel: prefer.s.defaultImageCompressionLevel,
|
||||
watermarkPresetId: prefer.s.defaultWatermarkPresetId,
|
||||
file: markRaw(file),
|
||||
};
|
||||
items.value.push(item);
|
||||
|
|
@ -572,6 +591,12 @@ onMounted(() => {
|
|||
initializeFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
for (const item of items.value) {
|
||||
URL.revokeObjectURL(item.thumbnail);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._watermarkEditor.position }}</template>
|
||||
<MkPositionSelector
|
||||
v-model:x="layer.alignX"
|
||||
v-model:y="layer.alignY"
|
||||
v-model:x="layer.align.x"
|
||||
v-model:y="layer.align.y"
|
||||
></MkPositionSelector>
|
||||
</FormSlot>
|
||||
|
||||
|
|
@ -50,8 +50,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._watermarkEditor.position }}</template>
|
||||
<MkPositionSelector
|
||||
v-model:x="layer.alignX"
|
||||
v-model:y="layer.alignY"
|
||||
v-model:x="layer.align.x"
|
||||
v-model:y="layer.align.y"
|
||||
></MkPositionSelector>
|
||||
</FormSlot>
|
||||
|
||||
|
|
@ -80,6 +80,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="layer.repeat">
|
||||
<template #label>{{ i18n.ts._watermarkEditor.repeat }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="layer.cover">
|
||||
<template #label>{{ i18n.ts._watermarkEditor.cover }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -87,9 +91,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
|
||||
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 { Watermarker } from '@/utility/watermarker.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkButton from '@/components/MkButton.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 { prefer } from '@/preferences.js';
|
||||
|
||||
const layer = defineModel<WatermarkerLayer>('layer', { required: true });
|
||||
const layer = defineModel<WatermarkPreset['layers'][number]>('layer', { required: true });
|
||||
|
||||
const driveFile = ref();
|
||||
const driveFileError = ref(false);
|
||||
|
|
|
|||
|
|
@ -45,27 +45,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
||||
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 { Watermarker } from '@/utility/watermarker.js';
|
||||
import { 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 MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import XLayer from '@/components/MkWatermarkEditorDialog.Layer.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';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const props = defineProps<{
|
||||
preset: WatermarkPreset | null;
|
||||
preset?: WatermarkPreset | null;
|
||||
}>();
|
||||
|
||||
const preset = reactive(deepClone(props.preset) ?? {
|
||||
|
|
@ -75,8 +71,7 @@ const preset = reactive(deepClone(props.preset) ?? {
|
|||
id: uuid(),
|
||||
type: 'text',
|
||||
text: `(c) @${$i.username}`,
|
||||
alignX: 'right',
|
||||
alignY: 'bottom',
|
||||
align: { x: 'right', y: 'bottom' },
|
||||
scale: 0.3,
|
||||
opacity: 0.75,
|
||||
repeat: false,
|
||||
|
|
@ -101,10 +96,9 @@ watch(type, () => {
|
|||
if (type.value === 'text') {
|
||||
preset.layers = [{
|
||||
id: uuid(),
|
||||
type: type.value,
|
||||
type: 'text',
|
||||
text: `(c) @${$i.username}`,
|
||||
alignX: 'right',
|
||||
alignY: 'bottom',
|
||||
align: { x: 'right', y: 'bottom' },
|
||||
scale: 0.3,
|
||||
opacity: 0.75,
|
||||
repeat: false,
|
||||
|
|
@ -112,11 +106,10 @@ watch(type, () => {
|
|||
} else if (type.value === 'image') {
|
||||
preset.layers = [{
|
||||
id: uuid(),
|
||||
type: type.value,
|
||||
type: 'image',
|
||||
imageId: null,
|
||||
imageUrl: null,
|
||||
alignX: 'right',
|
||||
alignY: 'bottom',
|
||||
align: { x: 'right', y: 'bottom' },
|
||||
scale: 0.3,
|
||||
opacity: 0.75,
|
||||
repeat: false,
|
||||
|
|
@ -126,7 +119,7 @@ watch(type, () => {
|
|||
|
||||
watch(preset, async (newValue, oldValue) => {
|
||||
if (renderer != null) {
|
||||
renderer.updatePreset(preset);
|
||||
renderer.updateLayers(makeImageEffectorLayers(preset.layers));
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
|
|
@ -153,25 +146,25 @@ watch(sampleImageType, async () => {
|
|||
}
|
||||
});
|
||||
|
||||
let renderer: Watermarker | null = null;
|
||||
let renderer: ImageEffector | null = null;
|
||||
|
||||
async function initRenderer() {
|
||||
if (canvasEl.value == null) return;
|
||||
|
||||
if (sampleImageType.value === '3_2') {
|
||||
renderer = new Watermarker({
|
||||
renderer = new ImageEffector({
|
||||
canvas: canvasEl.value,
|
||||
width: 1500,
|
||||
height: 1000,
|
||||
preset: preset,
|
||||
layers: makeImageEffectorLayers(preset.layers),
|
||||
originalImage: sampleImage_3_2,
|
||||
});
|
||||
} else if (sampleImageType.value === '2_3') {
|
||||
renderer = new Watermarker({
|
||||
renderer = new ImageEffector({
|
||||
canvas: canvasEl.value,
|
||||
width: 1000,
|
||||
height: 1500,
|
||||
preset: preset,
|
||||
layers: makeImageEffectorLayers(preset.layers),
|
||||
originalImage: sampleImage_2_3,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
|
|
@ -288,7 +293,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, reactive } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
|
|
@ -301,7 +306,6 @@ import { i18n } from '@/i18n.js';
|
|||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import { useForm } from '@/composables/use-form.js';
|
||||
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
|
|
@ -370,6 +374,7 @@ const adForm = useForm({
|
|||
|
||||
const urlPreviewForm = useForm({
|
||||
urlPreviewEnabled: meta.urlPreviewEnabled,
|
||||
urlPreviewAllowRedirect: meta.urlPreviewAllowRedirect,
|
||||
urlPreviewTimeout: meta.urlPreviewTimeout,
|
||||
urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength,
|
||||
urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength,
|
||||
|
|
@ -378,6 +383,7 @@ const urlPreviewForm = useForm({
|
|||
}, async (state) => {
|
||||
await os.apiWithDialog('admin/update-meta', {
|
||||
urlPreviewEnabled: state.urlPreviewEnabled,
|
||||
urlPreviewAllowRedirect: state.urlPreviewAllowRedirect,
|
||||
urlPreviewTimeout: state.urlPreviewTimeout,
|
||||
urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength,
|
||||
urlPreviewRequireContentLength: state.urlPreviewRequireContentLength,
|
||||
|
|
|
|||
|
|
@ -22,13 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
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 * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import { Watermarker } from '@/utility/watermarker.js';
|
||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||
|
||||
const props = defineProps<{
|
||||
preset: WatermarkPreset;
|
||||
|
|
@ -64,18 +65,18 @@ const canvasEl = useTemplateRef('canvasEl');
|
|||
const sampleImage = new Image();
|
||||
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
||||
|
||||
let renderer: Watermarker | null = null;
|
||||
let renderer: ImageEffector | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
sampleImage.onload = async () => {
|
||||
watch(canvasEl, async () => {
|
||||
if (canvasEl.value == null) return;
|
||||
|
||||
renderer = new Watermarker({
|
||||
renderer = new ImageEffector({
|
||||
canvas: canvasEl.value,
|
||||
width: 1500,
|
||||
height: 1000,
|
||||
preset: props.preset,
|
||||
layers: makeImageEffectorLayers(props.preset.layers),
|
||||
originalImage: sampleImage,
|
||||
});
|
||||
|
||||
|
|
@ -95,7 +96,7 @@ onUnmounted(() => {
|
|||
|
||||
watch(() => props.preset, async () => {
|
||||
if (renderer != null) {
|
||||
renderer.updatePreset(props.preset);
|
||||
renderer.updateLayers(makeImageEffectorLayers(props.preset.layers));
|
||||
await renderer.bakeTextures();
|
||||
renderer.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,62 +39,107 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['default', 'upload', 'folder']">
|
||||
<FormLink @click="chooseUploadFolder()">
|
||||
<SearchLabel>{{ i18n.ts.uploadFolder }}</SearchLabel>
|
||||
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
|
||||
<template #suffixIcon><i class="ti ti-folder"></i></template>
|
||||
<SearchMarker :keywords="['general']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['default', 'upload', 'folder']">
|
||||
<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>
|
||||
</SearchMarker>
|
||||
|
||||
<FormLink to="/settings/drive/cleaner">
|
||||
{{ i18n.ts.drivecleaner }}
|
||||
</FormLink>
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['watermark', 'credit']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-copyright"></i></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>
|
||||
<SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
<SearchMarker :keywords="['image']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.image }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['watermark', 'credit']">
|
||||
<MkFolder>
|
||||
<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>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
|
@ -104,9 +149,10 @@ import { computed, defineAsyncComponent, ref } from 'vue';
|
|||
import * as Misskey from 'misskey-js';
|
||||
import tinycolor from 'tinycolor2';
|
||||
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 MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
|
|
@ -146,6 +192,22 @@ const meterStyle = computed(() => {
|
|||
});
|
||||
|
||||
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 => {
|
||||
capacity.value = info.capacity;
|
||||
|
|
@ -203,6 +265,10 @@ function onDeleteWatermarkPreset(id: string) {
|
|||
...prefer.s.watermarkPresets.slice(0, index),
|
||||
...prefer.s.watermarkPresets.slice(index + 1),
|
||||
]);
|
||||
|
||||
if (prefer.s.defaultWatermarkPresetId === id) {
|
||||
prefer.commit('defaultWatermarkPresetId', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { Plugin } from '@/plugin.js';
|
|||
import type { DeviceKind } from '@/utility/device-kind.js';
|
||||
import type { DeckProfile } from '@/deck.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';
|
||||
|
||||
/** サウンド設定 */
|
||||
|
|
@ -351,8 +351,16 @@ export const PREF_DEF = {
|
|||
default: [] as string[],
|
||||
},
|
||||
watermarkPresets: {
|
||||
accountDependent: true,
|
||||
default: [] as WatermarkPreset[],
|
||||
},
|
||||
defaultWatermarkPresetId: {
|
||||
accountDependent: true,
|
||||
default: null as WatermarkPreset['id'] | null,
|
||||
},
|
||||
defaultImageCompressionLevel: {
|
||||
default: 2,
|
||||
},
|
||||
|
||||
'sound.masterVolume': {
|
||||
default: 0.5,
|
||||
|
|
|
|||
|
|
@ -3,86 +3,72 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const IMAGE_ADD_SHADER = `#version 300 es
|
||||
precision highp float;
|
||||
import { getProxiedImageUrl } from '../media-proxy.js';
|
||||
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;
|
||||
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
|
||||
out vec4 out_color;
|
||||
type ParamTypeToPrimitive = {
|
||||
'number': number;
|
||||
'boolean': boolean;
|
||||
'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; };
|
||||
'seed': number;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 pixel = texture(u_texture_src, in_uv);
|
||||
type ImageEffectorFxParamDefs = Record<string, {
|
||||
type: keyof ParamTypeToPrimitive;
|
||||
default: any;
|
||||
}>;
|
||||
|
||||
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 = 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 function defineImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDefs>(fx: ImageEffectorFx<ID, P>) {
|
||||
return fx;
|
||||
}
|
||||
`;
|
||||
|
||||
export type WatermarkPreset = {
|
||||
id: string;
|
||||
name: string;
|
||||
layers: WatermarkerLayer[];
|
||||
export type ImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDefs> = {
|
||||
id: ID;
|
||||
shader: string;
|
||||
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;
|
||||
type: 'text';
|
||||
text: string;
|
||||
repeat: boolean;
|
||||
scale: number;
|
||||
alignX: 'left' | 'center' | 'right';
|
||||
alignY: 'top' | 'center' | 'bottom';
|
||||
opacity: number;
|
||||
fxId: FXID;
|
||||
params: {
|
||||
[key in keyof FX['params']]: ParamTypeToPrimitive[FX['params'][key]['type']];
|
||||
};
|
||||
|
||||
// for watermarkPlacement fx
|
||||
imageUrl?: string | null;
|
||||
text?: string | null;
|
||||
};
|
||||
|
||||
type WatermarkerImageLayer = {
|
||||
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 ImageEffectorLayer = ImageEffectorLayerOf<(typeof FXS)[number]['id'], Extract<(typeof FXS)[number], { id: (typeof FXS)[number]['id'] }>>;
|
||||
|
||||
export type WatermarkerLayer = WatermarkerTextLayer | WatermarkerImageLayer;
|
||||
|
||||
export class Watermarker {
|
||||
export class ImageEffector {
|
||||
private canvas: HTMLCanvasElement | null = null;
|
||||
private gl: WebGL2RenderingContext | null = null;
|
||||
private renderTextureProgram!: WebGLProgram;
|
||||
|
|
@ -90,19 +76,20 @@ export class Watermarker {
|
|||
private renderWidth!: number;
|
||||
private renderHeight!: number;
|
||||
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||
private preset: WatermarkPreset;
|
||||
private layers: ImageEffectorLayer[];
|
||||
private originalImageTexture: WebGLTexture;
|
||||
private resultTexture: WebGLTexture;
|
||||
private resultFrameBuffer: WebGLFramebuffer;
|
||||
private bakedTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
||||
private bakedTexturesForWatermarkFx: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
||||
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: {
|
||||
canvas: HTMLCanvasElement;
|
||||
width: number;
|
||||
height: number;
|
||||
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||
preset: WatermarkPreset;
|
||||
layers: ImageEffectorLayer[];
|
||||
}) {
|
||||
this.canvas = options.canvas;
|
||||
this.canvas.width = options.width;
|
||||
|
|
@ -110,7 +97,7 @@ export class Watermarker {
|
|||
this.renderWidth = options.width;
|
||||
this.renderHeight = options.height;
|
||||
this.originalImage = options.originalImage;
|
||||
this.preset = options.preset;
|
||||
this.layers = options.layers;
|
||||
this.texturesKey = this.calcTexturesKey();
|
||||
|
||||
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.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
this.resultTexture = this.createTexture();
|
||||
this.resultFrameBuffer = gl.createFramebuffer()!;
|
||||
|
||||
this.renderTextureProgram = this.initShaderProgram(`#version 300 es
|
||||
in vec2 position;
|
||||
out vec2 in_uv;
|
||||
|
|
@ -180,10 +164,10 @@ export class Watermarker {
|
|||
}
|
||||
|
||||
private calcTexturesKey() {
|
||||
return this.preset.layers.map(layer => {
|
||||
if (layer.type === 'image') {
|
||||
return layer.imageId;
|
||||
} else if (layer.type === 'text') {
|
||||
return this.layers.map(layer => {
|
||||
if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
|
||||
return layer.imageUrl;
|
||||
} else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
|
||||
return layer.text;
|
||||
}
|
||||
return '';
|
||||
|
|
@ -208,10 +192,10 @@ export class Watermarker {
|
|||
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);
|
||||
}
|
||||
this.bakedTextures.clear();
|
||||
this.bakedTexturesForWatermarkFx.clear();
|
||||
}
|
||||
|
||||
public async bakeTextures() {
|
||||
|
|
@ -224,14 +208,13 @@ export class Watermarker {
|
|||
|
||||
this.disposeBakedTextures();
|
||||
|
||||
for (const layer of this.preset.layers) {
|
||||
if (layer.type === 'image') {
|
||||
for (const layer of this.layers) {
|
||||
if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
|
||||
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = layer.imageUrl;
|
||||
img.src = getProxiedImageUrl(layer.imageUrl); // CORS対策
|
||||
});
|
||||
|
||||
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.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
this.bakedTextures.set(layer.id, {
|
||||
this.bakedTexturesForWatermarkFx.set(layer.id, {
|
||||
texture: texture,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
});
|
||||
} else if (layer.type === 'text') {
|
||||
} else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
|
||||
const measureCtx = window.document.createElement('canvas').getContext('2d')!;
|
||||
measureCtx.canvas.width = this.renderWidth;
|
||||
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.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
this.bakedTextures.set(layer.id, {
|
||||
this.bakedTexturesForWatermarkFx.set(layer.id, {
|
||||
texture: texture,
|
||||
width: textCtx.canvas.width,
|
||||
height: textCtx.canvas.height,
|
||||
|
|
@ -333,18 +316,19 @@ export class Watermarker {
|
|||
return shaderProgram;
|
||||
}
|
||||
|
||||
private renderTextOrImageLayer(layer: WatermarkerTextLayer | WatermarkerImageLayer) {
|
||||
private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
|
||||
const gl = this.gl;
|
||||
if (gl == null) {
|
||||
throw new Error('gl is not initialized');
|
||||
}
|
||||
|
||||
const watermarkTexture = this.bakedTextures.get(layer.id);
|
||||
if (watermarkTexture == null) {
|
||||
return;
|
||||
}
|
||||
const fx = FXS.find(fx => fx.id === layer.fxId);
|
||||
if (fx == null) 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;
|
||||
out vec2 in_uv;
|
||||
|
||||
|
|
@ -352,72 +336,36 @@ export class Watermarker {
|
|||
in_uv = (position + 1.0) / 2.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.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
||||
const u_texture_src = gl.getUniformLocation(shaderProgram, 'u_texture_src');
|
||||
gl.uniform1i(u_texture_src, 0);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, watermarkTexture.texture);
|
||||
const u_texture_watermark = gl.getUniformLocation(shaderProgram, 'u_texture_watermark');
|
||||
gl.uniform1i(u_texture_watermark, 1);
|
||||
|
||||
const u_resolution_src = gl.getUniformLocation(shaderProgram, 'u_resolution_src');
|
||||
gl.uniform2fv(u_resolution_src, [this.renderWidth, this.renderHeight]);
|
||||
|
||||
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);
|
||||
fx.main({
|
||||
gl: gl,
|
||||
program: shaderProgram,
|
||||
params: Object.fromEntries(
|
||||
Object.entries(fx.params).map(([key, param]) => {
|
||||
return [key, layer.params[key] ?? param.default];
|
||||
}),
|
||||
) as any,
|
||||
preTexture: preTexture,
|
||||
width: this.renderWidth,
|
||||
height: this.renderHeight,
|
||||
watermark: watermark,
|
||||
});
|
||||
|
||||
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() {
|
||||
const gl = this.gl;
|
||||
if (gl == null) {
|
||||
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.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
||||
|
|
@ -436,8 +384,31 @@ export class Watermarker {
|
|||
|
||||
// --------------------
|
||||
|
||||
for (const layer of this.preset.layers) {
|
||||
this.renderLayer(layer);
|
||||
let preTexture = this.originalImageTexture;
|
||||
|
||||
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.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.resultTexture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
public async updatePreset(preset: WatermarkPreset) {
|
||||
this.preset = preset;
|
||||
public async updateLayers(layers: ImageEffectorLayer[]) {
|
||||
this.layers = layers;
|
||||
|
||||
const newTexturesKey = this.calcTexturesKey();
|
||||
if (newTexturesKey !== this.texturesKey) {
|
||||
|
|
@ -469,11 +440,24 @@ export class Watermarker {
|
|||
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();
|
||||
gl.deleteProgram(this.renderTextureProgram);
|
||||
gl.deleteProgram(this.renderInvertedTextureProgram);
|
||||
gl.deleteTexture(this.originalImageTexture);
|
||||
gl.deleteTexture(this.resultTexture);
|
||||
gl.deleteFramebuffer(this.resultFrameBuffer);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
@ -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));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
@ -9,24 +9,12 @@ import { apiUrl } from '@@/js/config.js';
|
|||
import { $i } from '@/i.js';
|
||||
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
|
||||
export function misskeyApi<
|
||||
ResT = void,
|
||||
E extends Endpoint | NonNullable<string> = Endpoint,
|
||||
P extends AnyRequest<E> = E extends Endpoint ? Request<E> : never,
|
||||
_ResT = ResT extends void ? Response<E, P> : ResT,
|
||||
E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
|
||||
P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
|
||||
_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
|
||||
>(
|
||||
endpoint: E,
|
||||
data: P & { i?: string | null; } = {} as any,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -81,9 +81,15 @@ export function getConfig(): UserConfig {
|
|||
return {
|
||||
base: '/vite/',
|
||||
|
||||
// The console is shared with backend, so clearing the console will also clear the backend log.
|
||||
clearScreen: false,
|
||||
|
||||
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,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
// バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される
|
||||
// そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない
|
||||
|
|
|
|||
|
|
@ -7306,8 +7306,9 @@ export type operations = {
|
|||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
fileId?: string;
|
||||
url?: string;
|
||||
fileId: string;
|
||||
} | {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -8093,10 +8094,12 @@ export type operations = {
|
|||
admin___emoji___update: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
'application/json': ({
|
||||
/** Format: misskey:id */
|
||||
id?: string;
|
||||
name?: string;
|
||||
id: string;
|
||||
} | {
|
||||
name: string;
|
||||
}) & ({
|
||||
/** Format: misskey:id */
|
||||
fileId?: string;
|
||||
/** @description Use `null` to reset the category. */
|
||||
|
|
@ -8106,7 +8109,7 @@ export type operations = {
|
|||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
|
@ -8806,6 +8809,7 @@ export type operations = {
|
|||
uri: string;
|
||||
version: string;
|
||||
urlPreviewEnabled: boolean;
|
||||
urlPreviewAllowRedirect: boolean;
|
||||
urlPreviewTimeout: number;
|
||||
urlPreviewMaximumContentLength: number;
|
||||
urlPreviewRequireContentLength: boolean;
|
||||
|
|
@ -11533,6 +11537,7 @@ export type operations = {
|
|||
/** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */
|
||||
summalyProxy?: string | null;
|
||||
urlPreviewEnabled?: boolean;
|
||||
urlPreviewAllowRedirect?: boolean;
|
||||
urlPreviewTimeout?: number;
|
||||
urlPreviewMaximumContentLength?: number;
|
||||
urlPreviewRequireContentLength?: boolean;
|
||||
|
|
@ -16996,8 +17001,9 @@ export type operations = {
|
|||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
fileId?: string;
|
||||
url?: string;
|
||||
fileId: string;
|
||||
} | {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -23096,9 +23102,10 @@ export type operations = {
|
|||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
tokenId?: string;
|
||||
token?: string | null;
|
||||
};
|
||||
tokenId: string;
|
||||
} | ({
|
||||
token: string | null;
|
||||
});
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
|
@ -25784,7 +25791,12 @@ export type operations = {
|
|||
'notes___search-by-tag': {
|
||||
requestBody: {
|
||||
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 */
|
||||
reply?: boolean | null;
|
||||
/** @default null */
|
||||
|
|
@ -25802,10 +25814,7 @@ export type operations = {
|
|||
untilId?: string;
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
tag?: string;
|
||||
/** @description The outer arrays are chained with OR, the inner arrays are chained with AND. */
|
||||
query?: string[][];
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
|
@ -26890,9 +26899,10 @@ export type operations = {
|
|||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
pageId?: string;
|
||||
name?: string;
|
||||
username?: string;
|
||||
pageId: string;
|
||||
} | {
|
||||
name: string;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -28979,18 +28989,20 @@ export type operations = {
|
|||
users___followers: {
|
||||
requestBody: {
|
||||
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 */
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
/** @default 10 */
|
||||
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: {
|
||||
requestBody: {
|
||||
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 */
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
/** Format: misskey:id */
|
||||
userId?: string;
|
||||
username?: string;
|
||||
/** @description The local host is represented with `null`. */
|
||||
host?: string | null;
|
||||
birthday?: string | null;
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
|
@ -30337,13 +30351,15 @@ export type operations = {
|
|||
'users___search-by-username-and-host': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
'application/json': (({
|
||||
username: string | null;
|
||||
}) | ({
|
||||
host: string | null;
|
||||
})) & {
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
/** @default true */
|
||||
detail?: boolean;
|
||||
username?: string | null;
|
||||
host?: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -30395,14 +30411,17 @@ export type operations = {
|
|||
users___show: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
'application/json': ({
|
||||
/** Format: misskey:id */
|
||||
userId?: string;
|
||||
userIds?: string[];
|
||||
username?: string;
|
||||
userId: string;
|
||||
} | {
|
||||
userIds: string[];
|
||||
} | {
|
||||
username: string;
|
||||
}) & ({
|
||||
/** @description The local host is represented with `null`. */
|
||||
host?: string | null;
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
|
|
|||
369
pnpm-lock.yaml
369
pnpm-lock.yaml
|
|
@ -88,11 +88,11 @@ importers:
|
|||
packages/backend:
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: 3.815.0
|
||||
version: 3.815.0
|
||||
specifier: 3.817.0
|
||||
version: 3.817.0
|
||||
'@aws-sdk/lib-storage':
|
||||
specifier: 3.815.0
|
||||
version: 3.815.0(@aws-sdk/client-s3@3.815.0)
|
||||
specifier: 3.817.0
|
||||
version: 3.817.0(@aws-sdk/client-s3@3.817.0)
|
||||
'@discordapp/twemoji':
|
||||
specifier: 15.1.0
|
||||
version: 15.1.0
|
||||
|
|
@ -130,14 +130,14 @@ importers:
|
|||
specifier: 0.1.70
|
||||
version: 0.1.70
|
||||
'@nestjs/common':
|
||||
specifier: 11.1.1
|
||||
version: 11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
specifier: 11.1.2
|
||||
version: 11.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core':
|
||||
specifier: 11.1.1
|
||||
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)
|
||||
specifier: 11.1.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':
|
||||
specifier: 11.1.1
|
||||
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)
|
||||
specifier: 11.1.2
|
||||
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':
|
||||
specifier: 1.7.0
|
||||
version: 1.7.0
|
||||
|
|
@ -364,8 +364,8 @@ importers:
|
|||
specifier: 3.4.1
|
||||
version: 3.4.1
|
||||
re2:
|
||||
specifier: 1.21.5
|
||||
version: 1.21.5
|
||||
specifier: 1.22.1
|
||||
version: 1.22.1
|
||||
redis-info:
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0
|
||||
|
|
@ -406,8 +406,8 @@ importers:
|
|||
specifier: 2.1.0
|
||||
version: 2.1.0
|
||||
systeminformation:
|
||||
specifier: 5.26.1
|
||||
version: 5.26.1
|
||||
specifier: 5.27.1
|
||||
version: 5.27.1
|
||||
tinycolor2:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0
|
||||
|
|
@ -446,8 +446,8 @@ importers:
|
|||
specifier: 29.7.0
|
||||
version: 29.7.0
|
||||
'@nestjs/platform-express':
|
||||
specifier: 10.4.17
|
||||
version: 10.4.17(@nestjs/common@11.1.1(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1)
|
||||
specifier: 10.4.18
|
||||
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':
|
||||
specifier: 9.22.0
|
||||
version: 9.22.0(vue@3.5.14(typescript@5.8.3))
|
||||
|
|
@ -1570,51 +1570,51 @@ packages:
|
|||
'@aws-crypto/util@5.2.0':
|
||||
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
|
||||
|
||||
'@aws-sdk/client-s3@3.815.0':
|
||||
resolution: {integrity: sha512-tpJyXuGYIPHIu8G53jXQw3mN5ZK6LdL+tcEF3kRJuQ377Vbo+BSfqaizt9Qb3JuOGcNwCp83jd2LlmcXypN5fg==}
|
||||
'@aws-sdk/client-s3@3.817.0':
|
||||
resolution: {integrity: sha512-nZyjhlLMEXDs0ofWbpikI8tKoeKuuSgYcIb6eEZJk90Nt5HkkXn6nkWOs/kp2FdhpoGJyTILOVsDgdm7eutnLA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/client-sso@3.812.0':
|
||||
resolution: {integrity: sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==}
|
||||
'@aws-sdk/client-sso@3.817.0':
|
||||
resolution: {integrity: sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/core@3.812.0':
|
||||
resolution: {integrity: sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==}
|
||||
'@aws-sdk/core@3.816.0':
|
||||
resolution: {integrity: sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.812.0':
|
||||
resolution: {integrity: sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==}
|
||||
'@aws-sdk/credential-provider-env@3.816.0':
|
||||
resolution: {integrity: sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.812.0':
|
||||
resolution: {integrity: sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==}
|
||||
'@aws-sdk/credential-provider-http@3.816.0':
|
||||
resolution: {integrity: sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.812.0':
|
||||
resolution: {integrity: sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==}
|
||||
'@aws-sdk/credential-provider-ini@3.817.0':
|
||||
resolution: {integrity: sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.812.0':
|
||||
resolution: {integrity: sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==}
|
||||
'@aws-sdk/credential-provider-node@3.817.0':
|
||||
resolution: {integrity: sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.812.0':
|
||||
resolution: {integrity: sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==}
|
||||
'@aws-sdk/credential-provider-process@3.816.0':
|
||||
resolution: {integrity: sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.812.0':
|
||||
resolution: {integrity: sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==}
|
||||
'@aws-sdk/credential-provider-sso@3.817.0':
|
||||
resolution: {integrity: sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.812.0':
|
||||
resolution: {integrity: sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==}
|
||||
'@aws-sdk/credential-provider-web-identity@3.817.0':
|
||||
resolution: {integrity: sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/lib-storage@3.815.0':
|
||||
resolution: {integrity: sha512-67FgW0T/1UupfKYzASW/5JEvM3jq6aIEwHaJl56K4nBhwVBxr1lu/xVSZ80fA6lgysKHh5GAtQhueQCKwj53wA==}
|
||||
'@aws-sdk/lib-storage@3.817.0':
|
||||
resolution: {integrity: sha512-2zOO8+2EmiS049PjLSNdqmmZMQj7fzE1hZJ70A94vO+KNaVhVZYuMOOiOmwMw6ePkTCcFwK40vZIIXwEQQ1v1g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/client-s3': ^3.815.0
|
||||
'@aws-sdk/client-s3': ^3.817.0
|
||||
|
||||
'@aws-sdk/middleware-bucket-endpoint@3.808.0':
|
||||
resolution: {integrity: sha512-wEPlNcs8dir9lXbuviEGtSzYSxG/NRKQrJk5ybOc7OpPGHovsN+QhDOdY3lcjOFdwMTiMIG9foUkPz3zBpLB1A==}
|
||||
|
|
@ -1624,8 +1624,8 @@ packages:
|
|||
resolution: {integrity: sha512-YW1hySBolALMII6C8y7Z0CRG2UX1dGJjLEBNFeefhO/xP7ZuE1dvnmfJGaEuBMnvc3wkRS63VZ3aqX6sevM1CA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.815.0':
|
||||
resolution: {integrity: sha512-cv/BO7saBbHTrLMUJiClZHM/GB4xDBbJmZ70f9HwcNBP59tBB8TgF/vSyi8SdFM82TvRP+Zzi1AZ8hXcwElaCg==}
|
||||
'@aws-sdk/middleware-flexible-checksums@3.816.0':
|
||||
resolution: {integrity: sha512-kftcwDxB/VoCBsUiRgkm5CIuKbTfCN1WLPbis9LRwX3kQhKgGVxG2gG78SHk4TBB0qviWVAd/t+i/KaUgwiAcA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-host-header@3.804.0':
|
||||
|
|
@ -1644,32 +1644,32 @@ packages:
|
|||
resolution: {integrity: sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.812.0':
|
||||
resolution: {integrity: sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==}
|
||||
'@aws-sdk/middleware-sdk-s3@3.816.0':
|
||||
resolution: {integrity: sha512-jJ+EAXM7gnOwiCM6rrl4AUNY5urmtIsX7roTkxtb4DevJxcS+wFYRRg3/j33fQbuxQZrvk21HqxyZYx5UH70PA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-ssec@3.804.0':
|
||||
resolution: {integrity: sha512-Tk8jK0gOIUBvEPTz/wwSlP1V70zVQ3QYqsLPAjQRMO6zfOK9ax31dln3MgKvFDJxBydS2tS3wsn53v+brxDxTA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-user-agent@3.812.0':
|
||||
resolution: {integrity: sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==}
|
||||
'@aws-sdk/middleware-user-agent@3.816.0':
|
||||
resolution: {integrity: sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/nested-clients@3.812.0':
|
||||
resolution: {integrity: sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==}
|
||||
'@aws-sdk/nested-clients@3.817.0':
|
||||
resolution: {integrity: sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/region-config-resolver@3.808.0':
|
||||
resolution: {integrity: sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.812.0':
|
||||
resolution: {integrity: sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==}
|
||||
'@aws-sdk/signature-v4-multi-region@3.816.0':
|
||||
resolution: {integrity: sha512-idcr9NW86sSIXASSej3423Selu6fxlhhJJtMgpAqoCH/HJh1eQrONJwNKuI9huiruPE8+02pwxuePvLW46X2mw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/token-providers@3.812.0':
|
||||
resolution: {integrity: sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==}
|
||||
'@aws-sdk/token-providers@3.817.0':
|
||||
resolution: {integrity: sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/types@3.804.0':
|
||||
|
|
@ -1691,8 +1691,8 @@ packages:
|
|||
'@aws-sdk/util-user-agent-browser@3.804.0':
|
||||
resolution: {integrity: sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==}
|
||||
|
||||
'@aws-sdk/util-user-agent-node@3.812.0':
|
||||
resolution: {integrity: sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==}
|
||||
'@aws-sdk/util-user-agent-node@3.816.0':
|
||||
resolution: {integrity: sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
aws-crt: '>=1.0.0'
|
||||
|
|
@ -2818,8 +2818,8 @@ packages:
|
|||
resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@nestjs/common@11.1.1':
|
||||
resolution: {integrity: sha512-crzp+1qeZ5EGL0nFTPy9NrVMAaUWewV5AwtQyv6SQ9yQPXwRl9W9hm1pt0nAtUu5QbYMbSuo7lYcF81EjM+nCA==}
|
||||
'@nestjs/common@11.1.2':
|
||||
resolution: {integrity: sha512-cHh4OPH44PjaHM93D1jgE1HO/B7XTZVRDxy/cPuGgyMEA4p2zXO+qqcOgTMC5FYcp7dX9jLeCjXAU0ToFAnODw==}
|
||||
peerDependencies:
|
||||
class-transformer: '>=0.4.1'
|
||||
class-validator: '>=0.13.2'
|
||||
|
|
@ -2831,8 +2831,8 @@ packages:
|
|||
class-validator:
|
||||
optional: true
|
||||
|
||||
'@nestjs/core@11.1.1':
|
||||
resolution: {integrity: sha512-UFoUAgLKFT+RwHTANJdr0dF7p0qS9QjkaUPjg8aafnjM/qxxxrUVDB49nVvyMlk+Hr1+vvcNaOHbWWQBxoZcHA==}
|
||||
'@nestjs/core@11.1.2':
|
||||
resolution: {integrity: sha512-QRuyxwu0BjNfmmmunsw1ylX7RSyfDQHt+xD+tKncdtgiMOOzAu+LA1gB4WoZnw4frQkk+qZbhEbM61cIjOxD3w==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^11.0.0
|
||||
|
|
@ -2849,14 +2849,14 @@ packages:
|
|||
'@nestjs/websockets':
|
||||
optional: true
|
||||
|
||||
'@nestjs/platform-express@10.4.17':
|
||||
resolution: {integrity: sha512-ovn4Wxney3QGBrqNPv0QLcCuH5QoAi6pb/GNWAz6B/NmBjZbs9/zl4a2beGDA2SaYre9w43YbfmHTm17PneP9w==}
|
||||
'@nestjs/platform-express@10.4.18':
|
||||
resolution: {integrity: sha512-v+W+Pu5NOVK/bSG5A5mOnXyoVwN5mJUe4o0j0UJ9Ig9JMmjVxg+Zw2ydTfpOQ+R82lRYWJUjjv3dvqKaFW2z7w==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^10.0.0
|
||||
'@nestjs/core': ^10.0.0
|
||||
|
||||
'@nestjs/testing@11.1.1':
|
||||
resolution: {integrity: sha512-stzm8YrLDGAijHYQw+8Z9dD6lGdvahL0hIjGVZ/0KBxLZht0/rvRjgV31UK+DUqXaF7yhJTw9ryrPaITxI1J6A==}
|
||||
'@nestjs/testing@11.1.2':
|
||||
resolution: {integrity: sha512-BQxVKUVW6gzEbbHAvmg5RgcP3s++pRgTCmsgaDF/DtcLRUeKi8SjAdqzLm14xbkMeibxOf3fNqM2iwqUKj8ffw==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^11.0.0
|
||||
'@nestjs/core': ^11.0.0
|
||||
|
|
@ -6448,9 +6448,9 @@ packages:
|
|||
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
file-type@20.5.0:
|
||||
resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==}
|
||||
engines: {node: '>=18'}
|
||||
file-type@21.0.0:
|
||||
resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
filename-reserved-regex@3.0.0:
|
||||
resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
|
||||
|
|
@ -6989,8 +6989,8 @@ packages:
|
|||
inspect-with-kind@1.0.5:
|
||||
resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==}
|
||||
|
||||
install-artifact-from-github@1.3.5:
|
||||
resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==}
|
||||
install-artifact-from-github@1.4.0:
|
||||
resolution: {integrity: sha512-+y6WywKZREw5rq7U2jvr2nmZpT7cbWbQQ0N/qfcseYnzHFz2cZz1Et52oY+XttYuYeTkI8Y+R2JNWj68MpQFSg==}
|
||||
hasBin: true
|
||||
|
||||
internal-slot@1.0.7:
|
||||
|
|
@ -8072,10 +8072,9 @@ packages:
|
|||
muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
|
||||
multer@1.4.4-lts.1:
|
||||
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
|
||||
engines: {node: '>= 6.0.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.
|
||||
multer@2.0.0:
|
||||
resolution: {integrity: sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==}
|
||||
engines: {node: '>= 10.16.0'}
|
||||
|
||||
mute-stream@2.0.0:
|
||||
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
|
||||
|
|
@ -9094,8 +9093,8 @@ packages:
|
|||
resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
re2@1.21.5:
|
||||
resolution: {integrity: sha512-ud7gX1bO6K4+l2YVUxZjOPCiyCBZvmi7XUnGArSk3rGIvsZW35jX3pjGs8zQiTumOpgbxHCZI1ivB1VO7i4MFw==}
|
||||
re2@1.22.1:
|
||||
resolution: {integrity: sha512-E4J0EtgyNLdIr0wTg0dQPefuiqNY29KaLacytiUAYYRzxCG+zOkWoUygt1rI+TA1LrhN49/njrfSO1DHtVC5Vw==}
|
||||
|
||||
react-docgen-typescript@2.2.2:
|
||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||
|
|
@ -9898,8 +9897,8 @@ packages:
|
|||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
systeminformation@5.26.1:
|
||||
resolution: {integrity: sha512-Nd503zsVvWKBREk5ekpCqONR6EVeualuZNm1ZS2BBCX/f/AUsOmsF32UVHUj8CUghWQle+6MdelIxRENGDvGhA==}
|
||||
systeminformation@5.27.1:
|
||||
resolution: {integrity: sha512-FgkVpT6GgATtNvADgtEzDxI/SVaBisfnQ4fmgQZhCJ4335noTgt9q6O81ioHwzs9HgnJaaFSdHSEMIkneZ55iA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
||||
hasBin: true
|
||||
|
|
@ -10527,6 +10526,9 @@ packages:
|
|||
vue-component-type-helpers@2.2.10:
|
||||
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:
|
||||
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -10919,29 +10921,29 @@ snapshots:
|
|||
'@smithy/util-utf8': 2.0.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/client-s3@3.815.0':
|
||||
'@aws-sdk/client-s3@3.817.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha1-browser': 5.2.0
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/credential-provider-node': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/credential-provider-node': 3.817.0
|
||||
'@aws-sdk/middleware-bucket-endpoint': 3.808.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-location-constraint': 3.804.0
|
||||
'@aws-sdk/middleware-logger': 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-user-agent': 3.812.0
|
||||
'@aws-sdk/middleware-user-agent': 3.816.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/util-endpoints': 3.808.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
|
||||
'@smithy/config-resolver': 4.1.3
|
||||
'@smithy/core': 3.4.0
|
||||
|
|
@ -10980,20 +10982,20 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sso@3.812.0':
|
||||
'@aws-sdk/client-sso@3.817.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 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-logger': 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/types': 3.804.0
|
||||
'@aws-sdk/util-endpoints': 3.808.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/core': 3.4.0
|
||||
'@smithy/fetch-http-handler': 5.0.3
|
||||
|
|
@ -11023,7 +11025,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/core@3.812.0':
|
||||
'@aws-sdk/core@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/core': 3.4.0
|
||||
|
|
@ -11037,17 +11039,17 @@ snapshots:
|
|||
fast-xml-parser: 4.4.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.812.0':
|
||||
'@aws-sdk/credential-provider-env@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/property-provider': 4.0.3
|
||||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.812.0':
|
||||
'@aws-sdk/credential-provider-http@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/fetch-http-handler': 5.0.3
|
||||
'@smithy/node-http-handler': 4.0.5
|
||||
|
|
@ -11058,15 +11060,15 @@ snapshots:
|
|||
'@smithy/util-stream': 4.2.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.812.0':
|
||||
'@aws-sdk/credential-provider-ini@3.817.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/credential-provider-env': 3.812.0
|
||||
'@aws-sdk/credential-provider-http': 3.812.0
|
||||
'@aws-sdk/credential-provider-process': 3.812.0
|
||||
'@aws-sdk/credential-provider-sso': 3.812.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.812.0
|
||||
'@aws-sdk/nested-clients': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/credential-provider-env': 3.816.0
|
||||
'@aws-sdk/credential-provider-http': 3.816.0
|
||||
'@aws-sdk/credential-provider-process': 3.816.0
|
||||
'@aws-sdk/credential-provider-sso': 3.817.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.817.0
|
||||
'@aws-sdk/nested-clients': 3.817.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/credential-provider-imds': 4.0.5
|
||||
'@smithy/property-provider': 4.0.3
|
||||
|
|
@ -11076,14 +11078,14 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.812.0':
|
||||
'@aws-sdk/credential-provider-node@3.817.0':
|
||||
dependencies:
|
||||
'@aws-sdk/credential-provider-env': 3.812.0
|
||||
'@aws-sdk/credential-provider-http': 3.812.0
|
||||
'@aws-sdk/credential-provider-ini': 3.812.0
|
||||
'@aws-sdk/credential-provider-process': 3.812.0
|
||||
'@aws-sdk/credential-provider-sso': 3.812.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.812.0
|
||||
'@aws-sdk/credential-provider-env': 3.816.0
|
||||
'@aws-sdk/credential-provider-http': 3.816.0
|
||||
'@aws-sdk/credential-provider-ini': 3.817.0
|
||||
'@aws-sdk/credential-provider-process': 3.816.0
|
||||
'@aws-sdk/credential-provider-sso': 3.817.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.817.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/credential-provider-imds': 4.0.5
|
||||
'@smithy/property-provider': 4.0.3
|
||||
|
|
@ -11093,20 +11095,20 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.812.0':
|
||||
'@aws-sdk/credential-provider-process@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/property-provider': 4.0.3
|
||||
'@smithy/shared-ini-file-loader': 4.0.3
|
||||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.812.0':
|
||||
'@aws-sdk/credential-provider-sso@3.817.0':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sso': 3.812.0
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/token-providers': 3.812.0
|
||||
'@aws-sdk/client-sso': 3.817.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/token-providers': 3.817.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/property-provider': 4.0.3
|
||||
'@smithy/shared-ini-file-loader': 4.0.3
|
||||
|
|
@ -11115,10 +11117,10 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.812.0':
|
||||
'@aws-sdk/credential-provider-web-identity@3.817.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@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
|
||||
'@smithy/property-provider': 4.0.3
|
||||
'@smithy/types': 4.3.0
|
||||
|
|
@ -11126,9 +11128,9 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@aws-sdk/client-s3': 3.815.0
|
||||
'@aws-sdk/client-s3': 3.817.0
|
||||
'@smithy/abort-controller': 4.0.3
|
||||
'@smithy/middleware-endpoint': 4.1.7
|
||||
'@smithy/smithy-client': 4.3.0
|
||||
|
|
@ -11154,12 +11156,12 @@ snapshots:
|
|||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.815.0':
|
||||
'@aws-sdk/middleware-flexible-checksums@3.816.0':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@aws-crypto/crc32c': 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
|
||||
'@smithy/is-array-buffer': 4.0.0
|
||||
'@smithy/node-config-provider': 4.1.2
|
||||
|
|
@ -11196,9 +11198,9 @@ snapshots:
|
|||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.812.0':
|
||||
'@aws-sdk/middleware-sdk-s3@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@aws-sdk/util-arn-parser': 3.804.0
|
||||
'@smithy/core': 3.4.0
|
||||
|
|
@ -11219,9 +11221,9 @@ snapshots:
|
|||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-user-agent@3.812.0':
|
||||
'@aws-sdk/middleware-user-agent@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.812.0
|
||||
'@aws-sdk/core': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@aws-sdk/util-endpoints': 3.808.0
|
||||
'@smithy/core': 3.4.0
|
||||
|
|
@ -11229,20 +11231,20 @@ snapshots:
|
|||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/nested-clients@3.812.0':
|
||||
'@aws-sdk/nested-clients@3.817.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 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-logger': 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/types': 3.804.0
|
||||
'@aws-sdk/util-endpoints': 3.808.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/core': 3.4.0
|
||||
'@smithy/fetch-http-handler': 5.0.3
|
||||
|
|
@ -11281,18 +11283,19 @@ snapshots:
|
|||
'@smithy/util-middleware': 4.0.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.812.0':
|
||||
'@aws-sdk/signature-v4-multi-region@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/middleware-sdk-s3': 3.812.0
|
||||
'@aws-sdk/middleware-sdk-s3': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/protocol-http': 5.1.1
|
||||
'@smithy/signature-v4': 5.1.0
|
||||
'@smithy/types': 4.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/token-providers@3.812.0':
|
||||
'@aws-sdk/token-providers@3.817.0':
|
||||
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
|
||||
'@smithy/property-provider': 4.0.3
|
||||
'@smithy/shared-ini-file-loader': 4.0.3
|
||||
|
|
@ -11328,9 +11331,9 @@ snapshots:
|
|||
bowser: 2.11.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-user-agent-node@3.812.0':
|
||||
'@aws-sdk/util-user-agent-node@3.816.0':
|
||||
dependencies:
|
||||
'@aws-sdk/middleware-user-agent': 3.812.0
|
||||
'@aws-sdk/middleware-user-agent': 3.816.0
|
||||
'@aws-sdk/types': 3.804.0
|
||||
'@smithy/node-config-provider': 4.1.2
|
||||
'@smithy/types': 4.3.0
|
||||
|
|
@ -11361,7 +11364,7 @@ snapshots:
|
|||
'@babel/traverse': 7.24.7
|
||||
'@babel/types': 7.25.6
|
||||
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
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
|
|
@ -11547,7 +11550,7 @@ snapshots:
|
|||
'@babel/helper-split-export-declaration': 7.24.7
|
||||
'@babel/parser': 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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -11810,7 +11813,7 @@ snapshots:
|
|||
'@eslint/config-array@0.20.0':
|
||||
dependencies:
|
||||
'@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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -11824,7 +11827,7 @@ snapshots:
|
|||
'@eslint/eslintrc@3.3.1':
|
||||
dependencies:
|
||||
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
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.1
|
||||
|
|
@ -12561,9 +12564,9 @@ snapshots:
|
|||
'@napi-rs/canvas-linux-x64-musl': 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:
|
||||
file-type: 20.5.0
|
||||
file-type: 21.0.0
|
||||
iterare: 1.2.1
|
||||
load-esm: 1.0.2
|
||||
reflect-metadata: 0.2.2
|
||||
|
|
@ -12573,9 +12576,9 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@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
|
||||
fast-safe-stringify: 2.1.1
|
||||
iterare: 1.2.1
|
||||
|
|
@ -12585,27 +12588,27 @@ snapshots:
|
|||
tslib: 2.8.1
|
||||
uid: 2.0.2
|
||||
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:
|
||||
'@nestjs/common': 11.1.1(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/common': 11.1.2(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
|
||||
cors: 2.8.5
|
||||
express: 4.21.2
|
||||
multer: 1.4.4-lts.1
|
||||
multer: 2.0.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@nestjs/common': 11.1.1(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/common': 11.1.2(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
|
||||
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': {}
|
||||
|
||||
|
|
@ -14042,7 +14045,7 @@ snapshots:
|
|||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
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)':
|
||||
dependencies:
|
||||
|
|
@ -14286,7 +14289,7 @@ snapshots:
|
|||
|
||||
'@tokenizer/inflate@0.2.7':
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
fflate: 0.8.2
|
||||
token-types: 6.0.0
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -14672,7 +14675,7 @@ snapshots:
|
|||
'@typescript-eslint/types': 8.32.1
|
||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||
'@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
|
||||
typescript: 5.8.3
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -14687,7 +14690,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@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)
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
eslint: 9.27.0
|
||||
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
|
|
@ -14700,7 +14703,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 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
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
|
|
@ -14737,7 +14740,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@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-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
|
|
@ -15063,7 +15066,7 @@ snapshots:
|
|||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
|
@ -16554,7 +16557,7 @@ snapshots:
|
|||
|
||||
esbuild-register@3.5.0(esbuild@0.25.4):
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
esbuild: 0.25.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -16701,7 +16704,7 @@ snapshots:
|
|||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
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
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
|
@ -17038,7 +17041,7 @@ snapshots:
|
|||
token-types: 6.0.0
|
||||
uint8array-extras: 1.4.0
|
||||
|
||||
file-type@20.5.0:
|
||||
file-type@21.0.0:
|
||||
dependencies:
|
||||
'@tokenizer/inflate': 0.2.7
|
||||
strtok3: 10.2.2
|
||||
|
|
@ -17114,7 +17117,7 @@ snapshots:
|
|||
|
||||
follow-redirects@1.15.9(debug@4.4.1):
|
||||
optionalDependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
|
||||
for-each@0.3.3:
|
||||
dependencies:
|
||||
|
|
@ -17512,7 +17515,7 @@ snapshots:
|
|||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -17540,7 +17543,7 @@ snapshots:
|
|||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
|
@ -17548,7 +17551,7 @@ snapshots:
|
|||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -17628,7 +17631,7 @@ snapshots:
|
|||
dependencies:
|
||||
kind-of: 6.0.3
|
||||
|
||||
install-artifact-from-github@1.3.5: {}
|
||||
install-artifact-from-github@1.4.0: {}
|
||||
|
||||
internal-slot@1.0.7:
|
||||
dependencies:
|
||||
|
|
@ -17642,7 +17645,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@ioredis/commands': 1.2.0
|
||||
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
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.isarguments: 3.1.0
|
||||
|
|
@ -17852,7 +17855,7 @@ snapshots:
|
|||
|
||||
istanbul-lib-source-maps@4.0.1:
|
||||
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
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -17861,7 +17864,7 @@ snapshots:
|
|||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -18877,7 +18880,7 @@ snapshots:
|
|||
micromark@4.0.0:
|
||||
dependencies:
|
||||
'@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
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.0
|
||||
|
|
@ -19079,7 +19082,7 @@ snapshots:
|
|||
|
||||
muggle-string@0.4.1: {}
|
||||
|
||||
multer@1.4.4-lts.1:
|
||||
multer@2.0.0:
|
||||
dependencies:
|
||||
append-field: 1.0.0
|
||||
busboy: 1.6.0
|
||||
|
|
@ -20092,9 +20095,9 @@ snapshots:
|
|||
dependencies:
|
||||
setimmediate: 1.0.5
|
||||
|
||||
re2@1.21.5:
|
||||
re2@1.22.1:
|
||||
dependencies:
|
||||
install-artifact-from-github: 1.3.5
|
||||
install-artifact-from-github: 1.4.0
|
||||
nan: 2.22.2
|
||||
node-gyp: 11.2.0
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -20266,7 +20269,7 @@ snapshots:
|
|||
|
||||
require-in-the-middle@7.3.0:
|
||||
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
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -20623,7 +20626,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@hapi/hoek': 11.0.4
|
||||
'@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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -20723,7 +20726,7 @@ snapshots:
|
|||
socks-proxy-agent@8.0.5:
|
||||
dependencies:
|
||||
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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -20820,7 +20823,7 @@ snapshots:
|
|||
arg: 5.0.2
|
||||
bluebird: 3.7.2
|
||||
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
|
||||
lazy-ass: 1.6.0
|
||||
ps-tree: 1.2.0
|
||||
|
|
@ -20998,7 +21001,7 @@ snapshots:
|
|||
dependencies:
|
||||
component-emitter: 1.3.1
|
||||
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
|
||||
form-data: 4.0.2
|
||||
formidable: 3.5.4
|
||||
|
|
@ -21048,7 +21051,7 @@ snapshots:
|
|||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
systeminformation@5.26.1: {}
|
||||
systeminformation@5.27.1: {}
|
||||
|
||||
tar-fs@2.1.2:
|
||||
dependencies:
|
||||
|
|
@ -21343,7 +21346,7 @@ snapshots:
|
|||
app-root-path: 3.1.0
|
||||
buffer: 6.0.3
|
||||
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
|
||||
dotenv: 16.4.7
|
||||
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):
|
||||
dependencies:
|
||||
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
|
||||
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)
|
||||
|
|
@ -21588,7 +21591,7 @@ snapshots:
|
|||
'@vitest/spy': 3.1.4
|
||||
'@vitest/utils': 3.1.4
|
||||
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
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
|
@ -21654,6 +21657,8 @@ snapshots:
|
|||
|
||||
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)):
|
||||
dependencies:
|
||||
vue: 3.5.14(typescript@5.8.3)
|
||||
|
|
@ -21675,7 +21680,7 @@ snapshots:
|
|||
|
||||
vue-eslint-parser@10.1.3(eslint@9.27.0):
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
eslint: 9.27.0
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue