From 17da44078b247db02e9d2b0f4a69ec7781e6a5b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Oct 2025 04:19:47 +0000 Subject: [PATCH 01/79] [skip ci] Update CHANGELOG.md (prepend template) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2262400a4c..903724d99f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +### General +- + +### Client +- + +### Server +- + + ## 2025.10.2 ### Client From 4e16e23acd6db761ac7490c7a4c0b84cee490389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:41:03 +0900 Subject: [PATCH 02/79] =?UTF-8?q?fix(frontend):=20confetti=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E3=81=8C=E3=82=A2=E3=83=8B=E3=83=A1=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E8=A8=AD=E5=AE=9A=E3=82=92=E8=80=83?= =?UTF-8?q?=E6=85=AE=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#16714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): confettiの実行がアニメーション設定を考慮していない問題を修正 * Update Changelog --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 2 +- packages/frontend-shared/js/config.ts | 1 + packages/frontend/src/preferences/def.ts | 7 ++++--- packages/frontend/src/store.ts | 7 ++++--- packages/frontend/src/utility/confetti.ts | 12 ++++++++++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 903724d99f..ea10fb9e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 ### Server - diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts index 6272d3f6b9..8021c4f022 100644 --- a/packages/frontend-shared/js/config.ts +++ b/packages/frontend-shared/js/config.ts @@ -20,3 +20,4 @@ export const instanceName = (siteName === 'Misskey' || siteName == null) ? host export const ui = localStorage.getItem('ui'); export const debug = localStorage.getItem('debug') === 'true'; export const isSafeMode = localStorage.getItem('isSafeMode') === 'true'; +export const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)').matches; diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index ebd031b240..915b192605 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -5,6 +5,7 @@ import * as Misskey from 'misskey-js'; import { hemisphere } from '@@/js/intl-const.js'; +import { prefersReducedMotion } from '@@/js/config.js'; import { definePreferences } from './manager.js'; import type { Theme } from '@/theme.js'; import type { SoundType } from '@/utility/sound.js'; @@ -211,10 +212,10 @@ export const PREF_DEF = definePreferences({ default: false, }, animation: { - default: !window.matchMedia('(prefers-reduced-motion)').matches, + default: !prefersReducedMotion, }, animatedMfm: { - default: !window.matchMedia('(prefers-reduced-motion)').matches, + default: !prefersReducedMotion, }, advancedMfm: { default: true, @@ -232,7 +233,7 @@ export const PREF_DEF = definePreferences({ default: false, }, disableShowingAnimatedImages: { - default: window.matchMedia('(prefers-reduced-motion)').matches, + default: prefersReducedMotion, }, emojiStyle: { default: 'twemoji', // twemoji / fluentEmoji / native diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 87b2637a64..073fbba0fb 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -7,6 +7,7 @@ import { markRaw, ref } from 'vue'; import * as Misskey from 'misskey-js'; import lightTheme from '@@/themes/l-light.json5'; import darkTheme from '@@/themes/d-green-lime.json5'; +import { prefersReducedMotion } from '@@/js/config.js'; import { hemisphere } from '@@/js/intl-const.js'; import type { DeviceKind } from '@/utility/device-kind.js'; import type { Plugin } from '@/plugin.js'; @@ -220,11 +221,11 @@ export const store = markRaw(new Pizzax('base', { }, animation: { where: 'device', - default: !window.matchMedia('(prefers-reduced-motion)').matches, + default: !prefersReducedMotion, }, animatedMfm: { where: 'device', - default: !window.matchMedia('(prefers-reduced-motion)').matches, + default: !prefersReducedMotion, }, advancedMfm: { where: 'device', @@ -248,7 +249,7 @@ export const store = markRaw(new Pizzax('base', { }, disableShowingAnimatedImages: { where: 'device', - default: window.matchMedia('(prefers-reduced-motion)').matches, + default: prefersReducedMotion, }, emojiStyle: { where: 'device', diff --git a/packages/frontend/src/utility/confetti.ts b/packages/frontend/src/utility/confetti.ts index c19149875f..b95c26345e 100644 --- a/packages/frontend/src/utility/confetti.ts +++ b/packages/frontend/src/utility/confetti.ts @@ -5,13 +5,21 @@ import _confetti from 'canvas-confetti'; import * as os from '@/os.js'; +import { prefer } from '@/preferences.js'; export function confetti(options: { duration?: number; } = {}) { + if (!prefer.s.animation) return; + const duration = options.duration ?? 1000 * 4; const animationEnd = Date.now() + duration; - const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: os.claimZIndex('high') }; + const defaults = { + startVelocity: 30, + spread: 360, + ticks: 60, + zIndex: os.claimZIndex('high'), + } satisfies _confetti.Options; - function randomInRange(min, max) { + function randomInRange(min: number, max: number) { return Math.random() * (max - min) + min; } From d0a5ccc1ec47a82ffab871baaff8e0c31625de64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:41:17 +0900 Subject: [PATCH 03/79] =?UTF-8?q?fix(frontend):=20=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=AE=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88=E5=BF=98=E3=82=8C=20(#1671?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/about.overview.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue index 03d500044c..32296de3a4 100644 --- a/packages/frontend/src/pages/about.overview.vue +++ b/packages/frontend/src/pages/about.overview.vue @@ -137,6 +137,7 @@ import FormSplit from '@/components/form/split.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkLink from '@/components/MkLink.vue'; +import MkInfo from '@/components/MkInfo.vue'; const initStats = () => misskeyApi('stats', {}); From 231a6877be1fa6a5924f458da18e9122edbce13a Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:27:11 +0900 Subject: [PATCH 04/79] =?UTF-8?q?enhance:=20Node.js=20v24=E3=81=8CActive?= =?UTF-8?q?=20LTS=E3=81=AB=E7=A7=BB=E8=A1=8C=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E3=81=93=E3=81=A8=E3=81=AB=E4=BC=B4=E3=81=84v24=E3=82=92?= =?UTF-8?q?=E6=A8=99=E6=BA=96=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 2 +- .node-version | 2 +- CHANGELOG.md | 3 +- Dockerfile | 2 +- package.json | 5 +- packages/backend/package.json | 4 +- pnpm-lock.yaml | 647 +++++++++++++++++++++++++++++--- 7 files changed, 615 insertions(+), 50 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 514abdfb20..e800743469 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "22.15.0" + "version": "24.10.0" }, "ghcr.io/devcontainers-extra/features/pnpm:2": { "version": "10.10.0" diff --git a/.node-version b/.node-version index b8ffd70759..21651351e2 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.15.0 +24.10.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index ea10fb9e87..4b08a76093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Unreleased ### General -- +- Enhance: Node.js 24.10.0をサポートするようになりました +- Enhance: DockerのNode.jsが24.10.0に更新されました ### Client - Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 diff --git a/Dockerfile b/Dockerfile index 370bed5751..20e24d1dc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=22.15.0-bookworm +ARG NODE_VERSION=24.10.0-bookworm # build assets & compile TypeScript diff --git a/package.json b/package.json index f8760026f6..a7acb3e049 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,9 @@ "pnpm": { "overrides": { "@aiscript-dev/aiscript-languageserver": "-" - } + }, + "ignoredBuiltDependencies": [ + "@sentry-internal/node-cpu-profiler" + ] } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 3228d7b096..37dde5a4e6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "engines": { - "node": "^22.15.0" + "node": "^22.15.0 || ^24.10.0" }, "scripts": { "start": "node ./built/boot/entry.js", @@ -88,7 +88,7 @@ "@nestjs/testing": "11.1.6", "@peertube/http-signature": "1.7.0", "@sentry/node": "8.55.0", - "@sentry/profiling-node": "8.55.0", + "@sentry/profiling-node": "10.20.0", "@simplewebauthn/server": "12.0.0", "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78676c24f4..ad4477265f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,8 +148,8 @@ importers: specifier: 8.55.0 version: 8.55.0 '@sentry/profiling-node': - specifier: 8.55.0 - version: 8.55.0 + specifier: 10.20.0 + version: 10.20.0 '@simplewebauthn/server': specifier: 12.0.0 version: 12.0.0(encoding@0.1.13) @@ -1593,6 +1593,12 @@ packages: peerDependencies: '@types/json-schema': ^7.0.15 + '@apm-js-collab/code-transformer@0.8.2': + resolution: {integrity: sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==} + + '@apm-js-collab/tracing-hooks@0.3.1': + resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} + '@asamuzakjp/css-color@2.8.3': resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} @@ -2966,6 +2972,10 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api-logs@0.204.0': + resolution: {integrity: sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.53.0': resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==} engines: {node: '>=14'} @@ -2988,36 +2998,72 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/context-async-hooks@2.1.0': + resolution: {integrity: sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@1.30.1': resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.1.0': + resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/instrumentation-amqplib@0.46.0': resolution: {integrity: sha512-04VHHV1KIN/c1wLWwzmLI02d/welgscBJ4BuDqrHaxd+ZIdlVXK9UYQsYf3JwSeF52z/4YoSzr8bfdVBSWoMAg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-amqplib@0.51.0': + resolution: {integrity: sha512-XGmjYwjVRktD4agFnWBWQXo9SiYHKBxR6Ag3MLXwtLE4R99N3a08kGKM5SC1qOFKIELcQDGFEFT9ydXMH00Luw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-connect@0.43.0': resolution: {integrity: sha512-Q57JGpH6T4dkYHo9tKXONgLtxzsh1ZEW5M9A/OwKrZFyEpLqWgjhcZ3hIuVvDlhb426iDF1f9FPToV/mi5rpeA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-connect@0.48.0': + resolution: {integrity: sha512-OMjc3SFL4pC16PeK+tDhwP7MRvDPalYCGSvGqUhX5rASkI2H0RuxZHOWElYeXkV0WP+70Gw6JHWac/2Zqwmhdw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-dataloader@0.16.0': resolution: {integrity: sha512-88+qCHZC02up8PwKHk0UQKLLqGGURzS3hFQBZC7PnGwReuoKjHXS1o29H58S+QkXJpkTr2GACbx8j6mUoGjNPA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-dataloader@0.22.0': + resolution: {integrity: sha512-bXnTcwtngQsI1CvodFkTemrrRSQjAjZxqHVc+CJZTDnidT0T6wt3jkKhnsjU/Kkkc0lacr6VdRpCu2CUWa0OKw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-express@0.47.0': resolution: {integrity: sha512-XFWVx6k0XlU8lu6cBlCa29ONtVt6ADEjmxtyAyeF2+rifk8uBJbk1La0yIVfI0DoKURGbaEDTNelaXG9l/lNNQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-express@0.53.0': + resolution: {integrity: sha512-r/PBafQmFYRjuxLYEHJ3ze1iBnP2GDA1nXOSS6E02KnYNZAVjj6WcDA1MSthtdAUUK0XnotHvvWM8/qz7DMO5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-fastify@0.44.1': resolution: {integrity: sha512-RoVeMGKcNttNfXMSl6W4fsYoCAYP1vi6ZAWIGhBY+o7R9Y0afA7f9JJL0j8LHbyb0P0QhSYk+6O56OwI2k4iRQ==} engines: {node: '>=14'} @@ -3030,24 +3076,54 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-fs@0.24.0': + resolution: {integrity: sha512-HjIxJ6CBRD770KNVaTdMXIv29Sjz4C1kPCCK5x1Ujpc6SNnLGPqUVyJYZ3LUhhnHAqdbrl83ogVWjCgeT4Q0yw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-generic-pool@0.43.0': resolution: {integrity: sha512-at8GceTtNxD1NfFKGAuwtqM41ot/TpcLh+YsGe4dhf7gvv1HW/ZWdq6nfRtS6UjIvZJOokViqLPJ3GVtZItAnQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-generic-pool@0.48.0': + resolution: {integrity: sha512-TLv/On8pufynNR+pUbpkyvuESVASZZKMlqCm4bBImTpXKTpqXaJJ3o/MUDeMlM91rpen+PEv2SeyOKcHCSlgag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-graphql@0.47.0': resolution: {integrity: sha512-Cc8SMf+nLqp0fi8oAnooNEfwZWFnzMiBHCGmDFYqmgjPylyLmi83b+NiTns/rKGwlErpW0AGPt0sMpkbNlzn8w==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-graphql@0.52.0': + resolution: {integrity: sha512-3fEJ8jOOMwopvldY16KuzHbRhPk8wSsOTSF0v2psmOCGewh6ad+ZbkTx/xyUK9rUdUMWAxRVU0tFpj4Wx1vkPA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-hapi@0.45.1': resolution: {integrity: sha512-VH6mU3YqAKTePPfUPwfq4/xr049774qWtfTuJqVHoVspCLiT3bW+fCQ1toZxt6cxRPYASoYaBsMA3CWo8B8rcw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-hapi@0.51.0': + resolution: {integrity: sha512-qyf27DaFNL1Qhbo/da+04MSCw982B02FhuOS5/UF+PMhM61CcOiu7fPuXj8TvbqyReQuJFljXE6UirlvoT/62g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.204.0': + resolution: {integrity: sha512-1afJYyGRA4OmHTv0FfNTrTAzoEjPQUYgd+8ih/lX0LlZBnGio/O80vxA0lN3knsJPS7FiDrsDrWq25K7oAzbkw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-http@0.57.1': resolution: {integrity: sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==} engines: {node: '>=14'} @@ -3060,6 +3136,18 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-ioredis@0.52.0': + resolution: {integrity: sha512-rUvlyZwI90HRQPYicxpDGhT8setMrlHKokCtBtZgYxQWRF5RBbG4q0pGtbZvd7kyseuHbFpA3I/5z7M8b/5ywg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.14.0': + resolution: {integrity: sha512-kbB5yXS47dTIdO/lfbbXlzhvHFturbux4EpP0+6H78Lk0Bn4QXiZQW7rmZY1xBCY16mNcCb8Yt0mhz85hTnSVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-kafkajs@0.7.0': resolution: {integrity: sha512-LB+3xiNzc034zHfCtgs4ITWhq6Xvdo8bsq7amR058jZlf2aXXDrN9SV4si4z2ya9QX4tz6r4eZJwDkXOp14/AQ==} engines: {node: '>=14'} @@ -3072,42 +3160,84 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-knex@0.49.0': + resolution: {integrity: sha512-NKsRRT27fbIYL4Ix+BjjP8h4YveyKc+2gD6DMZbr5R5rUeDqfC8+DTfIt3c3ex3BIc5Vvek4rqHnN7q34ZetLQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-koa@0.47.0': resolution: {integrity: sha512-HFdvqf2+w8sWOuwtEXayGzdZ2vWpCKEQv5F7+2DSA74Te/Cv4rvb2E5So5/lh+ok4/RAIPuvCbCb/SHQFzMmbw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-koa@0.52.0': + resolution: {integrity: sha512-JJSBYLDx/mNSy8Ibi/uQixu2rH0bZODJa8/cz04hEhRaiZQoeJ5UrOhO/mS87IdgVsHrnBOsZ6vDu09znupyuA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-lru-memoizer@0.44.0': resolution: {integrity: sha512-Tn7emHAlvYDFik3vGU0mdwvWJDwtITtkJ+5eT2cUquct6nIs+H8M47sqMJkCpyPe5QIBJoTOHxmc6mj9lz6zDw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-lru-memoizer@0.49.0': + resolution: {integrity: sha512-ctXu+O/1HSadAxtjoEg2w307Z5iPyLOMM8IRNwjaKrIpNAthYGSOanChbk1kqY6zU5CrpkPHGdAT6jk8dXiMqw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongodb@0.51.0': resolution: {integrity: sha512-cMKASxCX4aFxesoj3WK8uoQ0YUrRvnfxaO72QWI2xLu5ZtgX/QvdGBlU3Ehdond5eb74c2s1cqRQUIptBnKz1g==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongodb@0.57.0': + resolution: {integrity: sha512-KD6Rg0KSHWDkik+qjIOWoksi1xqSpix8TSPfquIK1DTmd9OTFb5PHmMkzJe16TAPVEuElUW8gvgP59cacFcrMQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongoose@0.46.0': resolution: {integrity: sha512-mtVv6UeaaSaWTeZtLo4cx4P5/ING2obSqfWGItIFSunQBrYROfhuVe7wdIrFUs2RH1tn2YYpAJyMaRe/bnTTIQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongoose@0.51.0': + resolution: {integrity: sha512-gwWaAlhhV2By7XcbyU3DOLMvzsgeaymwP/jktDC+/uPkCmgB61zurwqOQdeiRq9KAf22Y2dtE5ZLXxytJRbEVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql2@0.45.0': resolution: {integrity: sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql2@0.51.0': + resolution: {integrity: sha512-zT2Wg22Xn43RyfU3NOUmnFtb5zlDI0fKcijCj9AcK9zuLZ4ModgtLXOyBJSSfO+hsOCZSC1v/Fxwj+nZJFdzLQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql@0.45.0': resolution: {integrity: sha512-tWWyymgwYcTwZ4t8/rLDfPYbOTF3oYB8SxnYMtIQ1zEf5uDm90Ku3i6U/vhaMyfHNlIHvDhvJh+qx5Nc4Z3Acg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql@0.50.0': + resolution: {integrity: sha512-duKAvMRI3vq6u9JwzIipY9zHfikN20bX05sL7GjDeLKr2qV0LQ4ADtKST7KStdGcQ+MTN5wghWbbVdLgNcB3rA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-nestjs-core@0.44.0': resolution: {integrity: sha512-t16pQ7A4WYu1yyQJZhRKIfUNvl5PAaF2pEteLvgJb/BWdd1oNuU1rOYt4S825kMy+0q4ngiX281Ss9qiwHfxFQ==} engines: {node: '>=14'} @@ -3120,24 +3250,54 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-pg@0.57.0': + resolution: {integrity: sha512-dWLGE+r5lBgm2A8SaaSYDE3OKJ/kwwy5WLyGyzor8PLhUL9VnJRiY6qhp4njwhnljiLtzeffRtG2Mf/YyWLeTw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-redis-4@0.46.0': resolution: {integrity: sha512-aTUWbzbFMFeRODn3720TZO0tsh/49T8H3h8vVnVKJ+yE36AeW38Uj/8zykQ/9nO8Vrtjr5yKuX3uMiG/W8FKNw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-redis@0.53.0': + resolution: {integrity: sha512-WUHV8fr+8yo5RmzyU7D5BIE1zwiaNQcTyZPwtxlfr7px6NYYx7IIpSihJK7WA60npWynfxxK1T67RAVF0Gdfjg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-tedious@0.18.0': resolution: {integrity: sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-tedious@0.23.0': + resolution: {integrity: sha512-3TMTk/9VtlRonVTaU4tCzbg4YqW+Iq/l5VnN2e5whP6JgEg/PKfrGbqQ+CxQWNLfLaQYIUgEZqAn5gk/inh1uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-undici@0.10.0': resolution: {integrity: sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.7.0 + '@opentelemetry/instrumentation-undici@0.15.0': + resolution: {integrity: sha512-sNFGA/iCDlVkNjzTzPRcudmI11vT/WAfAguRdZY9IspCw02N4WSC72zTuQhSMheh2a1gdeM9my1imnKRvEEvEg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation@0.204.0': + resolution: {integrity: sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.53.0': resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==} engines: {node: '>=14'} @@ -3160,18 +3320,34 @@ packages: resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} engines: {node: '>=14'} + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + '@opentelemetry/resources@1.30.1': resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/resources@2.1.0': + resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@1.30.1': resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.1.0': + resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.27.0': resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==} engines: {node: '>=14'} @@ -3180,12 +3356,22 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.37.0': + resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.40.1': resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.1.0 + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} @@ -3303,6 +3489,11 @@ packages: '@prisma/instrumentation@5.22.0': resolution: {integrity: sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==} + '@prisma/instrumentation@6.15.0': + resolution: {integrity: sha512-6TXaH6OmDkMOQvOxwLZ8XS51hU2v4A3vmE2pSijCIiGRJYyNeMcL6nMHQMyYdZRD8wl7LF3Wzc+AMPMV/9Oo7A==} + peerDependencies: + '@opentelemetry/api': ^1.8 + '@readme/better-ajv-errors@2.3.2': resolution: {integrity: sha512-T4GGnRAlY3C339NhoUpgJJFsMYko9vIgFAlhgV+/vEGFw66qEY4a4TRJIAZBcX/qT1pq5DvXSme+SQODHOoBrw==} engines: {node: '>=18'} @@ -3532,6 +3723,10 @@ packages: resolution: {integrity: sha512-KLRy3OolDkGdPItQ3obtBU2RqDt9+KE8z7r7Gsu7c6A6A89m8ZVlrxee3hPQt6qp0YY0P8WazpedU3DYTtaT8w==} engines: {node: '>=18'} + '@sentry-internal/node-cpu-profiler@2.2.0': + resolution: {integrity: sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==} + engines: {node: '>=18'} + '@sentry-internal/replay-canvas@10.19.0': resolution: {integrity: sha512-DulLU4lvtrGPExKtpbCveLxPACrFmGx4eEYhzIn35UH8iIx6ONRSLemQyiUJQoLau7KXJy0I8AWxN+SagfebEA==} engines: {node: '>=18'} @@ -3560,6 +3755,10 @@ packages: resolution: {integrity: sha512-OqZjYDYsK6ZmBG5UzML0uKiKq//G6mMwPcszfuCsFgPt+pg5giUCrCUbt5VIVkHdN1qEEBk321JO2haU5n2Eig==} engines: {node: '>=18'} + '@sentry/core@10.20.0': + resolution: {integrity: sha512-S291KihnOIB8i7mVJIJBVHBMcCfIoY/KDJBHEfBoHY9M56g2An4FVhM9+/xR85+IoMkTySdXN08k9LEyQz4FpQ==} + engines: {node: '>=18'} + '@sentry/core@8.55.0': resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==} engines: {node: '>=14.18'} @@ -3568,10 +3767,36 @@ packages: resolution: {integrity: sha512-it7JMFqxVproAgEtbLgCVBYtQ9fIb+Bu0JD+cEplTN/Ukpe6GaolyYib5geZqslVxhp2sQgT+58aGvfd/k0N8Q==} engines: {node: '>=18'} + '@sentry/node-core@10.20.0': + resolution: {integrity: sha512-9BelcS9722jionzuyUNff4Bqx6fMFlYlK+5gMZYxAzOdS1AYZeFiYNVV2GlCGXfDLSHTnE1rOGH6QOsUdgCdbg==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/resources': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.37.0 + + '@sentry/node@10.20.0': + resolution: {integrity: sha512-Hv6cxQ2ilL54lF6+WwvvrvJEkt0fwUAAQZNwfAR0CfuP4Zg8FPQvxDeLhryd2Qr0nwPMNi3eYjypORjD3dcMLg==} + engines: {node: '>=18'} + '@sentry/node@8.55.0': resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==} engines: {node: '>=14.18'} + '@sentry/opentelemetry@10.20.0': + resolution: {integrity: sha512-91hr3RbMSUWgZb1BpW0gjlPsFaPtx0oNY2HYoC12T//1E0RMV183McBKbghBdT9swjhw112jeTFEERMJCaJyNw==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.37.0 + '@sentry/opentelemetry@8.55.0': resolution: {integrity: sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ==} engines: {node: '>=14.18'} @@ -3583,9 +3808,9 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 '@opentelemetry/semantic-conventions': ^1.28.0 - '@sentry/profiling-node@8.55.0': - resolution: {integrity: sha512-rYrlxbMlfQLHhkBUEC7bviuja1rojCb4+TtXi4NGnB4PppZeveGeuVTdJDWt3Ed6IBd20EEYoXv4+0aETbEnpw==} - engines: {node: '>=14.18'} + '@sentry/profiling-node@10.20.0': + resolution: {integrity: sha512-Ov+GmBSGlv+OWEaMps31M952/hGKLaqjKeF3KiIAVzo6SnzAhbylaItcCiQyk5ZFyTrVWBKGp2VbaN0/79yZ3A==} + engines: {node: '>=18'} hasBin: true '@sentry/vue@10.19.0': @@ -4477,6 +4702,9 @@ packages: '@types/connect@3.4.36': resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/content-disposition@0.5.9': resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} @@ -4600,6 +4828,9 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} @@ -7198,8 +7429,8 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@1.11.2: - resolution: {integrity: sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==} + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} @@ -8307,8 +8538,8 @@ packages: resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==} engines: {node: '>= 8'} - module-details-from-path@1.0.3: - resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -11205,6 +11436,16 @@ snapshots: '@types/json-schema': 7.0.15 js-yaml: 4.1.0 + '@apm-js-collab/code-transformer@0.8.2': {} + + '@apm-js-collab/tracing-hooks@0.3.1': + dependencies: + '@apm-js-collab/code-transformer': 0.8.2 + debug: 4.4.3(supports-color@10.2.0) + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + '@asamuzakjp/css-color@2.8.3': dependencies: '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) @@ -13228,6 +13469,10 @@ snapshots: '@open-draft/until@2.1.0': {} + '@opentelemetry/api-logs@0.204.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.53.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -13246,17 +13491,35 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/instrumentation-amqplib@0.46.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-amqplib@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13265,11 +13528,21 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 '@types/connect': 3.4.36 transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-connect@0.48.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-dataloader@0.16.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13277,12 +13550,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-dataloader@0.22.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-express@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.53.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13291,7 +13580,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13303,6 +13592,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-fs@0.24.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-generic-pool@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13310,6 +13607,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-generic-pool@0.48.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-graphql@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13317,12 +13621,38 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-graphql@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-hapi@0.45.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.204.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color @@ -13342,7 +13672,24 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.14.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13350,7 +13697,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13358,7 +13705,15 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.49.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13367,7 +13722,16 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13378,11 +13742,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.49.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongodb@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13391,7 +13770,16 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13399,25 +13787,43 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql2@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mysql@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 '@types/mysql': 2.15.26 transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql@0.50.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-nestjs-core@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13433,12 +13839,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-pg@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.5 + '@types/pg-pool': 2.0.6 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-redis-4@0.46.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.53.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color @@ -13446,7 +13873,16 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.23.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color @@ -13459,12 +13895,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-undici@0.15.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.204.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.204.0 + import-in-the-middle: 1.15.0 + require-in-the-middle: 7.3.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.53.0 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.2 + import-in-the-middle: 1.15.0 require-in-the-middle: 7.3.0 semver: 7.7.3 shimmer: 1.2.1 @@ -13476,7 +13929,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.57.1 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.2 + import-in-the-middle: 1.15.0 require-in-the-middle: 7.3.0 semver: 7.7.3 shimmer: 1.2.1 @@ -13488,7 +13941,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.2 + import-in-the-middle: 1.15.0 require-in-the-middle: 7.3.0 semver: 7.7.3 shimmer: 1.2.1 @@ -13497,12 +13950,20 @@ snapshots: '@opentelemetry/redis-common@0.36.2': {} + '@opentelemetry/redis-common@0.38.2': {} + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13510,15 +13971,29 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions@1.27.0': {} '@opentelemetry/semantic-conventions@1.28.0': {} + '@opentelemetry/semantic-conventions@1.37.0': {} + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@paralleldrive/cuid2@2.2.2': dependencies: '@noble/hashes': 1.8.0 @@ -13635,6 +14110,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@prisma/instrumentation@6.15.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@readme/better-ajv-errors@2.3.2(ajv@8.17.1)': dependencies: '@babel/code-frame': 7.27.1 @@ -13828,6 +14310,11 @@ snapshots: dependencies: '@sentry/core': 9.46.0 + '@sentry-internal/node-cpu-profiler@2.2.0': + dependencies: + detect-libc: 2.1.1 + node-abi: 3.77.0 + '@sentry-internal/replay-canvas@10.19.0': dependencies: '@sentry-internal/replay': 10.19.0 @@ -13866,10 +14353,68 @@ snapshots: '@sentry/core@10.19.0': {} + '@sentry/core@10.20.0': {} + '@sentry/core@8.55.0': {} '@sentry/core@9.46.0': {} + '@sentry/node-core@10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.204.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': + dependencies: + '@apm-js-collab/tracing-hooks': 0.3.1 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@sentry/core': 10.20.0 + '@sentry/opentelemetry': 10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + import-in-the-middle: 1.15.0 + transitivePeerDependencies: + - supports-color + + '@sentry/node@10.20.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.48.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.22.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.24.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.48.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.204.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.14.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.49.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.49.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.50.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.23.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.15.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@prisma/instrumentation': 6.15.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.20.0 + '@sentry/node-core': 10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.204.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + '@sentry/opentelemetry': 10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + import-in-the-middle: 1.15.0 + minimatch: 9.0.5 + transitivePeerDependencies: + - supports-color + '@sentry/node@8.55.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -13902,30 +14447,38 @@ snapshots: '@opentelemetry/instrumentation-undici': 0.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 '@prisma/instrumentation': 5.22.0 '@sentry/core': 8.55.0 - '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) - import-in-the-middle: 1.11.2 + '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + import-in-the-middle: 1.15.0 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)': + '@sentry/opentelemetry@10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + '@sentry/core': 10.20.0 + + '@sentry/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.37.0 '@sentry/core': 8.55.0 - '@sentry/profiling-node@8.55.0': + '@sentry/profiling-node@10.20.0': dependencies: - '@sentry/core': 8.55.0 - '@sentry/node': 8.55.0 - detect-libc: 2.1.1 - node-abi: 3.77.0 + '@sentry-internal/node-cpu-profiler': 2.2.0 + '@sentry/core': 10.20.0 + '@sentry/node': 10.20.0 transitivePeerDependencies: - supports-color @@ -15007,7 +15560,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: - '@types/connect': 3.4.36 + '@types/connect': 3.4.38 '@types/node': 22.18.10 '@types/braces@3.0.1': {} @@ -15028,6 +15581,10 @@ snapshots: dependencies: '@types/node': 22.18.10 + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.18.10 + '@types/content-disposition@0.5.9': {} '@types/cookiejar@2.1.5': {} @@ -15151,6 +15708,10 @@ snapshots: dependencies: '@types/node': 22.18.10 + '@types/mysql@2.15.27': + dependencies: + '@types/node': 22.18.10 + '@types/node-fetch@2.6.11': dependencies: '@types/node': 22.18.10 @@ -18327,12 +18888,12 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.11.2: + import-in-the-middle@1.15.0: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.2.2 - module-details-from-path: 1.0.3 + module-details-from-path: 1.0.4 import-lazy@4.0.0: {} @@ -19783,7 +20344,7 @@ snapshots: mock-socket@9.3.1: {} - module-details-from-path@1.0.3: {} + module-details-from-path@1.0.4: {} ms@2.0.0: {} @@ -21039,7 +21600,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: debug: 4.4.3(supports-color@10.2.0) - module-details-from-path: 1.0.3 + module-details-from-path: 1.0.4 resolve: 1.22.10 transitivePeerDependencies: - supports-color From f1578c282e44b0940cf7d589226d13fde96c193e Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:43:59 +0900 Subject: [PATCH 05/79] update deps --- packages/backend/package.json | 6 +- packages/frontend/package.json | 2 +- pnpm-lock.yaml | 736 ++------------------------------- 3 files changed, 43 insertions(+), 701 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 37dde5a4e6..2da618777f 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -87,7 +87,7 @@ "@nestjs/core": "11.1.6", "@nestjs/testing": "11.1.6", "@peertube/http-signature": "1.7.0", - "@sentry/node": "8.55.0", + "@sentry/node": "10.20.0", "@sentry/profiling-node": "10.20.0", "@simplewebauthn/server": "12.0.0", "@sinonjs/fake-timers": "11.3.1", @@ -117,7 +117,7 @@ "fastify": "5.6.1", "fastify-raw-body": "5.0.0", "feed": "4.2.2", - "file-type": "19.6.0", + "file-type": "21.0.0", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.4", "got": "14.5.0", @@ -191,7 +191,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@nestjs/platform-express": "10.4.20", - "@sentry/vue": "9.46.0", + "@sentry/vue": "10.20.0", "@simplewebauthn/types": "12.0.0", "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index e67c1e9b33..be1bf7f0c0 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,7 +24,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", "@rollup/pluginutils": "5.3.0", - "@sentry/vue": "10.19.0", + "@sentry/vue": "10.20.0", "@syuilo/aiscript": "1.1.2", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@twemoji/parser": "16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad4477265f..c7e88906ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 1.7.0 version: 1.7.0 '@sentry/node': - specifier: 8.55.0 - version: 8.55.0 + specifier: 10.20.0 + version: 10.20.0 '@sentry/profiling-node': specifier: 10.20.0 version: 10.20.0 @@ -235,8 +235,8 @@ importers: specifier: 4.2.2 version: 4.2.2 file-type: - specifier: 19.6.0 - version: 19.6.0 + specifier: 21.0.0 + version: 21.0.0 fluent-ffmpeg: specifier: 2.1.3 version: 2.1.3 @@ -452,8 +452,8 @@ importers: specifier: 10.4.20 version: 10.4.20(@nestjs/common@11.1.6(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) '@sentry/vue': - specifier: 9.46.0 - version: 9.46.0(vue@3.5.22(typescript@5.9.3)) + specifier: 10.20.0 + version: 10.20.0(vue@3.5.22(typescript@5.9.3)) '@simplewebauthn/types': specifier: 12.0.0 version: 12.0.0 @@ -726,8 +726,8 @@ importers: specifier: 5.3.0 version: 5.3.0(rollup@4.52.4) '@sentry/vue': - specifier: 10.19.0 - version: 10.19.0(vue@3.5.22(typescript@5.9.3)) + specifier: 10.20.0 + version: 10.20.0(vue@3.5.22(typescript@5.9.3)) '@syuilo/aiscript': specifier: 1.1.2 version: 1.1.2 @@ -2976,14 +2976,6 @@ packages: resolution: {integrity: sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==} engines: {node: '>=8.0.0'} - '@opentelemetry/api-logs@0.53.0': - resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==} - engines: {node: '>=14'} - - '@opentelemetry/api-logs@0.57.1': - resolution: {integrity: sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==} - engines: {node: '>=14'} - '@opentelemetry/api-logs@0.57.2': resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} engines: {node: '>=14'} @@ -2992,126 +2984,60 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@1.30.1': - resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/context-async-hooks@2.1.0': resolution: {integrity: sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.30.1': - resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.1.0': resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/instrumentation-amqplib@0.46.0': - resolution: {integrity: sha512-04VHHV1KIN/c1wLWwzmLI02d/welgscBJ4BuDqrHaxd+ZIdlVXK9UYQsYf3JwSeF52z/4YoSzr8bfdVBSWoMAg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-amqplib@0.51.0': resolution: {integrity: sha512-XGmjYwjVRktD4agFnWBWQXo9SiYHKBxR6Ag3MLXwtLE4R99N3a08kGKM5SC1qOFKIELcQDGFEFT9ydXMH00Luw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.43.0': - resolution: {integrity: sha512-Q57JGpH6T4dkYHo9tKXONgLtxzsh1ZEW5M9A/OwKrZFyEpLqWgjhcZ3hIuVvDlhb426iDF1f9FPToV/mi5rpeA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.48.0': resolution: {integrity: sha512-OMjc3SFL4pC16PeK+tDhwP7MRvDPalYCGSvGqUhX5rASkI2H0RuxZHOWElYeXkV0WP+70Gw6JHWac/2Zqwmhdw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-dataloader@0.16.0': - resolution: {integrity: sha512-88+qCHZC02up8PwKHk0UQKLLqGGURzS3hFQBZC7PnGwReuoKjHXS1o29H58S+QkXJpkTr2GACbx8j6mUoGjNPA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-dataloader@0.22.0': resolution: {integrity: sha512-bXnTcwtngQsI1CvodFkTemrrRSQjAjZxqHVc+CJZTDnidT0T6wt3jkKhnsjU/Kkkc0lacr6VdRpCu2CUWa0OKw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.47.0': - resolution: {integrity: sha512-XFWVx6k0XlU8lu6cBlCa29ONtVt6ADEjmxtyAyeF2+rifk8uBJbk1La0yIVfI0DoKURGbaEDTNelaXG9l/lNNQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.53.0': resolution: {integrity: sha512-r/PBafQmFYRjuxLYEHJ3ze1iBnP2GDA1nXOSS6E02KnYNZAVjj6WcDA1MSthtdAUUK0XnotHvvWM8/qz7DMO5A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fastify@0.44.1': - resolution: {integrity: sha512-RoVeMGKcNttNfXMSl6W4fsYoCAYP1vi6ZAWIGhBY+o7R9Y0afA7f9JJL0j8LHbyb0P0QhSYk+6O56OwI2k4iRQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-fs@0.19.0': - resolution: {integrity: sha512-JGwmHhBkRT2G/BYNV1aGI+bBjJu4fJUD/5/Jat0EWZa2ftrLV3YE8z84Fiij/wK32oMZ88eS8DI4ecLGZhpqsQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.24.0': resolution: {integrity: sha512-HjIxJ6CBRD770KNVaTdMXIv29Sjz4C1kPCCK5x1Ujpc6SNnLGPqUVyJYZ3LUhhnHAqdbrl83ogVWjCgeT4Q0yw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-generic-pool@0.43.0': - resolution: {integrity: sha512-at8GceTtNxD1NfFKGAuwtqM41ot/TpcLh+YsGe4dhf7gvv1HW/ZWdq6nfRtS6UjIvZJOokViqLPJ3GVtZItAnQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-generic-pool@0.48.0': resolution: {integrity: sha512-TLv/On8pufynNR+pUbpkyvuESVASZZKMlqCm4bBImTpXKTpqXaJJ3o/MUDeMlM91rpen+PEv2SeyOKcHCSlgag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.47.0': - resolution: {integrity: sha512-Cc8SMf+nLqp0fi8oAnooNEfwZWFnzMiBHCGmDFYqmgjPylyLmi83b+NiTns/rKGwlErpW0AGPt0sMpkbNlzn8w==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.52.0': resolution: {integrity: sha512-3fEJ8jOOMwopvldY16KuzHbRhPk8wSsOTSF0v2psmOCGewh6ad+ZbkTx/xyUK9rUdUMWAxRVU0tFpj4Wx1vkPA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.45.1': - resolution: {integrity: sha512-VH6mU3YqAKTePPfUPwfq4/xr049774qWtfTuJqVHoVspCLiT3bW+fCQ1toZxt6cxRPYASoYaBsMA3CWo8B8rcw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.51.0': resolution: {integrity: sha512-qyf27DaFNL1Qhbo/da+04MSCw982B02FhuOS5/UF+PMhM61CcOiu7fPuXj8TvbqyReQuJFljXE6UirlvoT/62g==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3124,18 +3050,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.57.1': - resolution: {integrity: sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-ioredis@0.47.0': - resolution: {integrity: sha512-4HqP9IBC8e7pW9p90P3q4ox0XlbLGme65YTrA3UTLvqvo4Z6b0puqZQP203YFu8m9rE/luLfaG7/xrwwqMUpJw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.52.0': resolution: {integrity: sha512-rUvlyZwI90HRQPYicxpDGhT8setMrlHKokCtBtZgYxQWRF5RBbG4q0pGtbZvd7kyseuHbFpA3I/5z7M8b/5ywg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3148,144 +3062,66 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-kafkajs@0.7.0': - resolution: {integrity: sha512-LB+3xiNzc034zHfCtgs4ITWhq6Xvdo8bsq7amR058jZlf2aXXDrN9SV4si4z2ya9QX4tz6r4eZJwDkXOp14/AQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-knex@0.44.0': - resolution: {integrity: sha512-SlT0+bLA0Lg3VthGje+bSZatlGHw/vwgQywx0R/5u9QC59FddTQSPJeWNw29M6f8ScORMeUOOTwihlQAn4GkJQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-knex@0.49.0': resolution: {integrity: sha512-NKsRRT27fbIYL4Ix+BjjP8h4YveyKc+2gD6DMZbr5R5rUeDqfC8+DTfIt3c3ex3BIc5Vvek4rqHnN7q34ZetLQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.47.0': - resolution: {integrity: sha512-HFdvqf2+w8sWOuwtEXayGzdZ2vWpCKEQv5F7+2DSA74Te/Cv4rvb2E5So5/lh+ok4/RAIPuvCbCb/SHQFzMmbw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.52.0': resolution: {integrity: sha512-JJSBYLDx/mNSy8Ibi/uQixu2rH0bZODJa8/cz04hEhRaiZQoeJ5UrOhO/mS87IdgVsHrnBOsZ6vDu09znupyuA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-lru-memoizer@0.44.0': - resolution: {integrity: sha512-Tn7emHAlvYDFik3vGU0mdwvWJDwtITtkJ+5eT2cUquct6nIs+H8M47sqMJkCpyPe5QIBJoTOHxmc6mj9lz6zDw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-lru-memoizer@0.49.0': resolution: {integrity: sha512-ctXu+O/1HSadAxtjoEg2w307Z5iPyLOMM8IRNwjaKrIpNAthYGSOanChbk1kqY6zU5CrpkPHGdAT6jk8dXiMqw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.51.0': - resolution: {integrity: sha512-cMKASxCX4aFxesoj3WK8uoQ0YUrRvnfxaO72QWI2xLu5ZtgX/QvdGBlU3Ehdond5eb74c2s1cqRQUIptBnKz1g==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.57.0': resolution: {integrity: sha512-KD6Rg0KSHWDkik+qjIOWoksi1xqSpix8TSPfquIK1DTmd9OTFb5PHmMkzJe16TAPVEuElUW8gvgP59cacFcrMQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.46.0': - resolution: {integrity: sha512-mtVv6UeaaSaWTeZtLo4cx4P5/ING2obSqfWGItIFSunQBrYROfhuVe7wdIrFUs2RH1tn2YYpAJyMaRe/bnTTIQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.51.0': resolution: {integrity: sha512-gwWaAlhhV2By7XcbyU3DOLMvzsgeaymwP/jktDC+/uPkCmgB61zurwqOQdeiRq9KAf22Y2dtE5ZLXxytJRbEVA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.45.0': - resolution: {integrity: sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.51.0': resolution: {integrity: sha512-zT2Wg22Xn43RyfU3NOUmnFtb5zlDI0fKcijCj9AcK9zuLZ4ModgtLXOyBJSSfO+hsOCZSC1v/Fxwj+nZJFdzLQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.45.0': - resolution: {integrity: sha512-tWWyymgwYcTwZ4t8/rLDfPYbOTF3oYB8SxnYMtIQ1zEf5uDm90Ku3i6U/vhaMyfHNlIHvDhvJh+qx5Nc4Z3Acg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.50.0': resolution: {integrity: sha512-duKAvMRI3vq6u9JwzIipY9zHfikN20bX05sL7GjDeLKr2qV0LQ4ADtKST7KStdGcQ+MTN5wghWbbVdLgNcB3rA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.44.0': - resolution: {integrity: sha512-t16pQ7A4WYu1yyQJZhRKIfUNvl5PAaF2pEteLvgJb/BWdd1oNuU1rOYt4S825kMy+0q4ngiX281Ss9qiwHfxFQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-pg@0.50.0': - resolution: {integrity: sha512-TtLxDdYZmBhFswm8UIsrDjh/HFBeDXd4BLmE8h2MxirNHewLJ0VS9UUddKKEverb5Sm2qFVjqRjcU+8Iw4FJ3w==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.57.0': resolution: {integrity: sha512-dWLGE+r5lBgm2A8SaaSYDE3OKJ/kwwy5WLyGyzor8PLhUL9VnJRiY6qhp4njwhnljiLtzeffRtG2Mf/YyWLeTw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis-4@0.46.0': - resolution: {integrity: sha512-aTUWbzbFMFeRODn3720TZO0tsh/49T8H3h8vVnVKJ+yE36AeW38Uj/8zykQ/9nO8Vrtjr5yKuX3uMiG/W8FKNw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis@0.53.0': resolution: {integrity: sha512-WUHV8fr+8yo5RmzyU7D5BIE1zwiaNQcTyZPwtxlfr7px6NYYx7IIpSihJK7WA60npWynfxxK1T67RAVF0Gdfjg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-tedious@0.18.0': - resolution: {integrity: sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-tedious@0.23.0': resolution: {integrity: sha512-3TMTk/9VtlRonVTaU4tCzbg4YqW+Iq/l5VnN2e5whP6JgEg/PKfrGbqQ+CxQWNLfLaQYIUgEZqAn5gk/inh1uQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-undici@0.10.0': - resolution: {integrity: sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.7.0 - '@opentelemetry/instrumentation-undici@0.15.0': resolution: {integrity: sha512-sNFGA/iCDlVkNjzTzPRcudmI11vT/WAfAguRdZY9IspCw02N4WSC72zTuQhSMheh2a1gdeM9my1imnKRvEEvEg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3298,74 +3134,32 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.53.0': - resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation@0.57.1': - resolution: {integrity: sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.57.2': resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/redis-common@0.36.2': - resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} - engines: {node: '>=14'} - '@opentelemetry/redis-common@0.38.2': resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@1.30.1': - resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/resources@2.1.0': resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@1.30.1': - resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.1.0': resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.27.0': - resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==} - engines: {node: '>=14'} - - '@opentelemetry/semantic-conventions@1.28.0': - resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} - engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.37.0': resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} engines: {node: '>=14'} - '@opentelemetry/sql-common@0.40.1': - resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@opentelemetry/sql-common@0.41.2': resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3486,9 +3280,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@prisma/instrumentation@5.22.0': - resolution: {integrity: sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==} - '@prisma/instrumentation@6.15.0': resolution: {integrity: sha512-6TXaH6OmDkMOQvOxwLZ8XS51hU2v4A3vmE2pSijCIiGRJYyNeMcL6nMHQMyYdZRD8wl7LF3Wzc+AMPMV/9Oo7A==} peerDependencies: @@ -3707,66 +3498,34 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sentry-internal/browser-utils@10.19.0': - resolution: {integrity: sha512-E3H6R+tX7sYMIjfCRAMO0qIH43dtUqv2rSo0vv6eHDi4lDXtlDc+Vb67n4VIesT7YVxQD7GIkNhMk3hmRDIwww==} + '@sentry-internal/browser-utils@10.20.0': + resolution: {integrity: sha512-9+NybrYs+dEM2iW5uRAYEhKkNK0XhDea5jovtDUXEvdSCMJFcdR88uztkftnCur45/hpvbgSULsGPUdHPb5ITw==} engines: {node: '>=18'} - '@sentry-internal/browser-utils@9.46.0': - resolution: {integrity: sha512-Q0CeHym9wysku8mYkORXmhtlBE0IrafAI+NiPSqxOBKXGOCWKVCvowHuAF56GwPFic2rSrRnub5fWYv7T1jfEQ==} - engines: {node: '>=18'} - - '@sentry-internal/feedback@10.19.0': - resolution: {integrity: sha512-AJ8rpzNYgfmWzovmFss51q9FtBaa2qYTLwkbVdTf58fZbLMUrgZ6qf9qMk0ePiS3nB87w9+mpbLzRObYOsK9RA==} - engines: {node: '>=18'} - - '@sentry-internal/feedback@9.46.0': - resolution: {integrity: sha512-KLRy3OolDkGdPItQ3obtBU2RqDt9+KE8z7r7Gsu7c6A6A89m8ZVlrxee3hPQt6qp0YY0P8WazpedU3DYTtaT8w==} + '@sentry-internal/feedback@10.20.0': + resolution: {integrity: sha512-R/eGLKl7WDccLKBorEbyTsy5b99w/k4v80SntE8HL2rsO7DCDXma8TGmtHd+iZnw8dUci+EVrw7LbeGSgf3QzA==} engines: {node: '>=18'} '@sentry-internal/node-cpu-profiler@2.2.0': resolution: {integrity: sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.19.0': - resolution: {integrity: sha512-DulLU4lvtrGPExKtpbCveLxPACrFmGx4eEYhzIn35UH8iIx6ONRSLemQyiUJQoLau7KXJy0I8AWxN+SagfebEA==} + '@sentry-internal/replay-canvas@10.20.0': + resolution: {integrity: sha512-8DBawFi4F4e2Cu2ToiitCnYsK8idrDOv66Vq+N6c8e3qFitTTuoPQwOihb2+HY4CB06ABPW3WvfZntJJmsf91w==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@9.46.0': - resolution: {integrity: sha512-QcBjrdRWFJrrrjbmrr2bbrp2R9RYj1KMEbhHNT2Lm1XplIQw+tULEKOHxNtkUFSLR1RNje7JQbxhzM1j95FxVQ==} + '@sentry-internal/replay@10.20.0': + resolution: {integrity: sha512-+XPYp0CuJnf+c36/c+hHrY6wAPHCdnqllZeyU7+9LAiKsdhN8Oo4eF1v5zd097qDZBg1NrKhU44ScJIzz+vygw==} engines: {node: '>=18'} - '@sentry-internal/replay@10.19.0': - resolution: {integrity: sha512-bOWsm/t+d2LCYa3gUjgwFds6kKSW+K6i4pssgDY4XiV/MxHsQtQ2rbHX80chLRQe2HFCX2njvjVSJN+Nsdjmpg==} - engines: {node: '>=18'} - - '@sentry-internal/replay@9.46.0': - resolution: {integrity: sha512-+8JUblxSSnN0FXcmOewbN+wIc1dt6/zaSeAvt2xshrfrLooVullcGsuLAiPhY0d/e++Fk06q1SAl9g4V0V13gg==} - engines: {node: '>=18'} - - '@sentry/browser@10.19.0': - resolution: {integrity: sha512-/+B84qFOLg1vJhg4YSA4a7Pneq5Pbt1BXEdrp/UW4tJmtGPZb28qXlMdoPfmFWZgVezrawaPkxLmbu+47/+rsQ==} - engines: {node: '>=18'} - - '@sentry/browser@9.46.0': - resolution: {integrity: sha512-NOnCTQCM0NFuwbyt4DYWDNO2zOTj1mCf43hJqGDFb1XM9F++7zAmSNnCx4UrEoBTiFOy40McJwBBk9D1blSktA==} - engines: {node: '>=18'} - - '@sentry/core@10.19.0': - resolution: {integrity: sha512-OqZjYDYsK6ZmBG5UzML0uKiKq//G6mMwPcszfuCsFgPt+pg5giUCrCUbt5VIVkHdN1qEEBk321JO2haU5n2Eig==} + '@sentry/browser@10.20.0': + resolution: {integrity: sha512-zcf8HwFiRbzjZL9KbLev44eEOf+yl+3svQbs2BlR2KAYGaB10swV5abij0UTTGO7ClnqUZdcGpwiyyfPS6mjHg==} engines: {node: '>=18'} '@sentry/core@10.20.0': resolution: {integrity: sha512-S291KihnOIB8i7mVJIJBVHBMcCfIoY/KDJBHEfBoHY9M56g2An4FVhM9+/xR85+IoMkTySdXN08k9LEyQz4FpQ==} engines: {node: '>=18'} - '@sentry/core@8.55.0': - resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==} - engines: {node: '>=14.18'} - - '@sentry/core@9.46.0': - resolution: {integrity: sha512-it7JMFqxVproAgEtbLgCVBYtQ9fIb+Bu0JD+cEplTN/Ukpe6GaolyYib5geZqslVxhp2sQgT+58aGvfd/k0N8Q==} - engines: {node: '>=18'} - '@sentry/node-core@10.20.0': resolution: {integrity: sha512-9BelcS9722jionzuyUNff4Bqx6fMFlYlK+5gMZYxAzOdS1AYZeFiYNVV2GlCGXfDLSHTnE1rOGH6QOsUdgCdbg==} engines: {node: '>=18'} @@ -3783,10 +3542,6 @@ packages: resolution: {integrity: sha512-Hv6cxQ2ilL54lF6+WwvvrvJEkt0fwUAAQZNwfAR0CfuP4Zg8FPQvxDeLhryd2Qr0nwPMNi3eYjypORjD3dcMLg==} engines: {node: '>=18'} - '@sentry/node@8.55.0': - resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==} - engines: {node: '>=14.18'} - '@sentry/opentelemetry@10.20.0': resolution: {integrity: sha512-91hr3RbMSUWgZb1BpW0gjlPsFaPtx0oNY2HYoC12T//1E0RMV183McBKbghBdT9swjhw112jeTFEERMJCaJyNw==} engines: {node: '>=18'} @@ -3797,34 +3552,13 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.37.0 - '@sentry/opentelemetry@8.55.0': - resolution: {integrity: sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ==} - engines: {node: '>=14.18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks': ^1.30.1 - '@opentelemetry/core': ^1.30.1 - '@opentelemetry/instrumentation': ^0.57.1 - '@opentelemetry/sdk-trace-base': ^1.30.1 - '@opentelemetry/semantic-conventions': ^1.28.0 - '@sentry/profiling-node@10.20.0': resolution: {integrity: sha512-Ov+GmBSGlv+OWEaMps31M952/hGKLaqjKeF3KiIAVzo6SnzAhbylaItcCiQyk5ZFyTrVWBKGp2VbaN0/79yZ3A==} engines: {node: '>=18'} hasBin: true - '@sentry/vue@10.19.0': - resolution: {integrity: sha512-ocMWDfRvOkc4avoSyM3GOSUXlUXqCSVxMvrTymQV8rBorkQ979i1Gxz5DLYjbPMqGoP7jBAFG+b4+ISYo5fTNg==} - engines: {node: '>=18'} - peerDependencies: - pinia: 2.x || 3.x - vue: 2.x || 3.x - peerDependenciesMeta: - pinia: - optional: true - - '@sentry/vue@9.46.0': - resolution: {integrity: sha512-xFeZevR2nG+4tdvZcVgO6U1YiTQJZJTtV8aKRsCEh4yYpBO3FrfLxbSMTUeipILfKxpFf2iu1lwmqNyQtEllkA==} + '@sentry/vue@10.20.0': + resolution: {integrity: sha512-3mWc81mLs1CpXrihuTQNXwt0HfOIy7BAZMvtYgaTuIDvooYtBbsZ+7yGDkcwsWBtfAvE7/yP2h95M4HRIw5KyQ==} engines: {node: '>=18'} peerDependencies: pinia: 2.x || 3.x @@ -4699,9 +4433,6 @@ packages: '@types/color-name@1.1.1': resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} - '@types/connect@3.4.36': - resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} - '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -4825,9 +4556,6 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/mysql@2.15.26': - resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} @@ -4867,9 +4595,6 @@ packages: '@types/pg@8.15.5': resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} - '@types/pg@8.6.1': - resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -13473,47 +13198,21 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs@0.53.0': - dependencies: - '@opentelemetry/api': 1.9.0 - - '@opentelemetry/api-logs@0.57.1': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs@0.57.2': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/instrumentation-amqplib@0.46.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-amqplib@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13523,16 +13222,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.43.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@types/connect': 3.4.36 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-connect@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13543,13 +13232,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.16.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-dataloader@0.22.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13557,15 +13239,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.47.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-express@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13575,23 +13248,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fastify@0.44.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-fs@0.19.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-fs@0.24.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13600,13 +13256,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.43.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-generic-pool@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13614,13 +13263,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.47.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-graphql@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13628,15 +13270,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.45.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-hapi@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13656,26 +13289,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.57.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - forwarded-parse: 2.1.2 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-ioredis@0.47.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-ioredis@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13693,22 +13306,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.7.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-knex@0.44.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-knex@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13717,15 +13314,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.47.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-koa@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13735,13 +13323,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.44.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13749,14 +13330,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.51.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mongodb@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13765,15 +13338,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.46.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mongoose@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13783,15 +13347,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.45.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mysql2@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13801,15 +13356,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.45.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@types/mysql': 2.15.26 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mysql@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13819,26 +13365,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.44.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-pg@0.50.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.27.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) - '@types/pg': 8.6.1 - '@types/pg-pool': 2.0.6 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-pg@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13851,15 +13377,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis-4@0.46.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-redis@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13869,15 +13386,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.18.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@types/tedious': 4.0.14 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-tedious@0.23.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13887,14 +13395,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.10.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-undici@0.15.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13912,30 +13412,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.53.0 - '@types/shimmer': 1.2.0 - import-in-the-middle: 1.15.0 - require-in-the-middle: 7.3.0 - semver: 7.7.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation@0.57.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.1 - '@types/shimmer': 1.2.0 - import-in-the-middle: 1.15.0 - require-in-the-middle: 7.3.0 - semver: 7.7.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13948,29 +13424,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/redis-common@0.36.2': {} - '@opentelemetry/redis-common@0.38.2': {} - '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13978,17 +13439,8 @@ snapshots: '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/semantic-conventions@1.27.0': {} - - '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.37.0': {} - '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14102,14 +13554,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/instrumentation@5.22.0': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@prisma/instrumentation@6.15.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14294,71 +13738,39 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@sentry-internal/browser-utils@10.19.0': + '@sentry-internal/browser-utils@10.20.0': dependencies: - '@sentry/core': 10.19.0 + '@sentry/core': 10.20.0 - '@sentry-internal/browser-utils@9.46.0': + '@sentry-internal/feedback@10.20.0': dependencies: - '@sentry/core': 9.46.0 - - '@sentry-internal/feedback@10.19.0': - dependencies: - '@sentry/core': 10.19.0 - - '@sentry-internal/feedback@9.46.0': - dependencies: - '@sentry/core': 9.46.0 + '@sentry/core': 10.20.0 '@sentry-internal/node-cpu-profiler@2.2.0': dependencies: detect-libc: 2.1.1 node-abi: 3.77.0 - '@sentry-internal/replay-canvas@10.19.0': + '@sentry-internal/replay-canvas@10.20.0': dependencies: - '@sentry-internal/replay': 10.19.0 - '@sentry/core': 10.19.0 + '@sentry-internal/replay': 10.20.0 + '@sentry/core': 10.20.0 - '@sentry-internal/replay-canvas@9.46.0': + '@sentry-internal/replay@10.20.0': dependencies: - '@sentry-internal/replay': 9.46.0 - '@sentry/core': 9.46.0 + '@sentry-internal/browser-utils': 10.20.0 + '@sentry/core': 10.20.0 - '@sentry-internal/replay@10.19.0': + '@sentry/browser@10.20.0': dependencies: - '@sentry-internal/browser-utils': 10.19.0 - '@sentry/core': 10.19.0 - - '@sentry-internal/replay@9.46.0': - dependencies: - '@sentry-internal/browser-utils': 9.46.0 - '@sentry/core': 9.46.0 - - '@sentry/browser@10.19.0': - dependencies: - '@sentry-internal/browser-utils': 10.19.0 - '@sentry-internal/feedback': 10.19.0 - '@sentry-internal/replay': 10.19.0 - '@sentry-internal/replay-canvas': 10.19.0 - '@sentry/core': 10.19.0 - - '@sentry/browser@9.46.0': - dependencies: - '@sentry-internal/browser-utils': 9.46.0 - '@sentry-internal/feedback': 9.46.0 - '@sentry-internal/replay': 9.46.0 - '@sentry-internal/replay-canvas': 9.46.0 - '@sentry/core': 9.46.0 - - '@sentry/core@10.19.0': {} + '@sentry-internal/browser-utils': 10.20.0 + '@sentry-internal/feedback': 10.20.0 + '@sentry-internal/replay': 10.20.0 + '@sentry-internal/replay-canvas': 10.20.0 + '@sentry/core': 10.20.0 '@sentry/core@10.20.0': {} - '@sentry/core@8.55.0': {} - - '@sentry/core@9.46.0': {} - '@sentry/node-core@10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.204.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': dependencies: '@apm-js-collab/tracing-hooks': 0.3.1 @@ -14415,46 +13827,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sentry/node@8.55.0': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-amqplib': 0.46.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.43.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-dataloader': 0.16.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.47.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fastify': 0.44.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fs': 0.19.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-generic-pool': 0.43.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-graphql': 0.47.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-hapi': 0.45.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.57.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-ioredis': 0.47.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-kafkajs': 0.7.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-knex': 0.44.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-koa': 0.47.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-lru-memoizer': 0.44.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongodb': 0.51.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongoose': 0.46.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql': 0.45.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql2': 0.45.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-nestjs-core': 0.44.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-pg': 0.50.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-redis-4': 0.46.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-tedious': 0.18.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-undici': 0.10.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@prisma/instrumentation': 5.22.0 - '@sentry/core': 8.55.0 - '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) - import-in-the-middle: 1.15.0 - transitivePeerDependencies: - - supports-color - '@sentry/opentelemetry@10.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14464,16 +13836,6 @@ snapshots: '@opentelemetry/semantic-conventions': 1.37.0 '@sentry/core': 10.20.0 - '@sentry/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@sentry/core': 8.55.0 - '@sentry/profiling-node@10.20.0': dependencies: '@sentry-internal/node-cpu-profiler': 2.2.0 @@ -14482,16 +13844,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@sentry/vue@10.19.0(vue@3.5.22(typescript@5.9.3))': + '@sentry/vue@10.20.0(vue@3.5.22(typescript@5.9.3))': dependencies: - '@sentry/browser': 10.19.0 - '@sentry/core': 10.19.0 - vue: 3.5.22(typescript@5.9.3) - - '@sentry/vue@9.46.0(vue@3.5.22(typescript@5.9.3))': - dependencies: - '@sentry/browser': 9.46.0 - '@sentry/core': 9.46.0 + '@sentry/browser': 10.20.0 + '@sentry/core': 10.20.0 vue: 3.5.22(typescript@5.9.3) '@shikijs/core@3.13.0': @@ -15577,10 +14933,6 @@ snapshots: '@types/color-name@1.1.1': {} - '@types/connect@3.4.36': - dependencies: - '@types/node': 22.18.10 - '@types/connect@3.4.38': dependencies: '@types/node': 22.18.10 @@ -15704,10 +15056,6 @@ snapshots: '@types/ms@0.7.34': {} - '@types/mysql@2.15.26': - dependencies: - '@types/node': 22.18.10 - '@types/mysql@2.15.27': dependencies: '@types/node': 22.18.10 @@ -15761,12 +15109,6 @@ snapshots: pg-protocol: 1.10.3 pg-types: 2.2.0 - '@types/pg@8.6.1': - dependencies: - '@types/node': 22.18.10 - pg-protocol: 1.10.3 - pg-types: 2.2.0 - '@types/prop-types@15.7.15': {} '@types/pug@2.0.10': {} From 3dfca2d61f1f08ee034b597d3b5fce24eec8277c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:05:14 +0900 Subject: [PATCH 06/79] lint --- packages/backend/src/core/FileInfoService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 62a7d24afb..af4d0b8c6b 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -20,8 +20,8 @@ import { AiService } from '@/core/AiService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; -import type { PredictionType } from 'nsfwjs'; import { isMimeImage } from '@/misc/is-mime-image.js'; +import type { PredictionType } from 'nsfwjs'; export type FileInfo = { size: number; @@ -339,7 +339,7 @@ export class FileInfoService { } @bindThis - public fixMime(mime: string | fileType.MimeType): string { + public fixMime(mime: string): string { // see https://github.com/misskey-dev/misskey/pull/10686 if (mime === 'audio/x-flac') { return 'audio/flac'; From 9e0f18a705cfd07841418cf2e0432832eeb559aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:34:42 +0900 Subject: [PATCH 07/79] deps: update `@types/node` to v24 [ci skip] (#16718) * update `@types/node` to v24 * fix --- package.json | 2 +- packages/backend/package.json | 2 +- packages/frontend-builder/package.json | 2 +- packages/frontend-embed/package.json | 2 +- packages/frontend-shared/package.json | 2 +- packages/frontend/package.json | 2 +- packages/icons-subsetter/package.json | 2 +- packages/misskey-bubble-game/package.json | 2 +- packages/misskey-js/generator/package.json | 2 +- packages/misskey-js/package.json | 2 +- packages/misskey-reversi/package.json | 2 +- pnpm-lock.yaml | 545 ++++++++++---------- scripts/changelog-checker/package-lock.json | 37 +- scripts/changelog-checker/package.json | 4 +- 14 files changed, 307 insertions(+), 301 deletions(-) diff --git a/package.json b/package.json index a7acb3e049..f6ff896a6d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", "@types/js-yaml": "4.0.9", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "cross-env": "10.1.0", diff --git a/packages/backend/package.json b/packages/backend/package.json index 2da618777f..dff9fe22cd 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -210,7 +210,7 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@types/nodemailer": "6.4.20", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", diff --git a/packages/frontend-builder/package.json b/packages/frontend-builder/package.json index 320e8b9cec..6952e9c907 100644 --- a/packages/frontend-builder/package.json +++ b/packages/frontend-builder/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@types/estree": "1.0.8", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "rollup": "4.52.4", diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index d4c922695b..561ce5f10e 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -43,7 +43,7 @@ "@testing-library/vue": "8.1.0", "@types/estree": "1.0.8", "@types/micromatch": "4.0.9", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 981e4b07f3..3b29856f79 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,7 +21,7 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "esbuild": "0.25.10", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index be1bf7f0c0..bd81d1d2c6 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -109,7 +109,7 @@ "@types/estree": "1.0.8", "@types/matter-js": "0.20.2", "@types/micromatch": "4.0.9", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.16.0", "@types/seedrandom": "3.0.8", diff --git a/packages/icons-subsetter/package.json b/packages/icons-subsetter/package.json index 284bced499..97c410826c 100644 --- a/packages/icons-subsetter/package.json +++ b/packages/icons-subsetter/package.json @@ -11,7 +11,7 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@types/wawoff2": "1.0.2", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1" diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json index ef05d67eff..934717571f 100644 --- a/packages/misskey-bubble-game/package.json +++ b/packages/misskey-bubble-game/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@types/matter-js": "0.20.2", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@types/seedrandom": "3.0.8", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json index fca323eac9..21ff792f99 100644 --- a/packages/misskey-js/generator/package.json +++ b/packages/misskey-js/generator/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@readme/openapi-parser": "5.0.2", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "openapi-types": "12.1.3", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index b000fd8418..acb9e0ea6e 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -36,7 +36,7 @@ }, "devDependencies": { "@microsoft/api-extractor": "7.53.1", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "@vitest/coverage-v8": "3.2.4", diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json index f050cf1fd9..6e4a770bb1 100644 --- a/packages/misskey-reversi/package.json +++ b/packages/misskey-reversi/package.json @@ -22,7 +22,7 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.1", "esbuild": "0.25.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e88906ed..b5ffd355e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -509,8 +509,8 @@ importers: specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@types/nodemailer': specifier: 6.4.20 version: 6.4.20 @@ -594,7 +594,7 @@ importers: version: 9.0.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@22.18.10) + version: 29.7.0(@types/node@24.9.1) jest-mock: specifier: 29.7.0 version: 29.7.0 @@ -739,7 +739,7 @@ importers: version: 16.0.0 '@vitejs/plugin-vue': specifier: 6.0.1 - version: 6.0.1(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) + version: 6.0.1(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) '@vue/compiler-sfc': specifier: 3.5.22 version: 3.5.22 @@ -892,7 +892,7 @@ importers: version: 1.13.1(vue@3.5.22(typescript@5.9.3)) vite: specifier: 7.1.9 - version: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + version: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vue: specifier: 3.5.22 version: 3.5.22(typescript@5.9.3) @@ -908,55 +908,55 @@ importers: version: 5.2.4 '@storybook/addon-essentials': specifier: 8.6.14 - version: 8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/addon-interactions': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/addon-links': specifier: 9.1.10 - version: 9.1.10(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 9.1.10(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/addon-mdx-gfm': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/addon-storysource': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/blocks': specifier: 8.6.14 - version: 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/components': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/core-events': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/manager-api': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/preview-api': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/react': specifier: 9.1.10 - version: 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3) + version: 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3) '@storybook/react-vite': specifier: 9.1.10 - version: 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.4)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.4)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@storybook/test': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/theming': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/types': specifier: 8.6.14 - version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + version: 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@storybook/vue3': specifier: 9.1.10 - version: 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3)) + version: 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3)) '@storybook/vue3-vite': specifier: 9.1.10 - version: 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) + version: 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) '@tabler/icons-webfont': specifier: 3.35.0 version: 3.35.0 @@ -976,8 +976,8 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@types/punycode.js': specifier: npm:@types/punycode@2.1.4 version: '@types/punycode@2.1.4' @@ -1004,7 +1004,7 @@ importers: version: 8.46.1(eslint@9.37.0)(typescript@5.9.3) '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@vue/compiler-core': specifier: 3.5.22 version: 3.5.22 @@ -1043,10 +1043,10 @@ importers: version: 10.0.3 msw: specifier: 2.11.5 - version: 2.11.5(@types/node@22.18.10)(typescript@5.9.3) + version: 2.11.5(@types/node@24.9.1)(typescript@5.9.3) msw-storybook-addon: specifier: 2.0.6 - version: 2.0.6(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3)) + version: 2.0.6(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3)) nodemon: specifier: 3.1.10 version: 3.1.10 @@ -1067,25 +1067,25 @@ importers: version: 2.1.2 storybook: specifier: 9.1.10 - version: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(eea6f1ff14bf28d064537fe27aa8f490) + version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(8592f997fbe36bffac29bb2d9605c63c) tsx: specifier: 4.20.6 version: 4.20.6 vite-plugin-glsl: specifier: 1.5.4 - version: 1.5.4(@rollup/pluginutils@5.3.0(rollup@4.52.4))(esbuild@0.25.10)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 1.5.4(@rollup/pluginutils@5.3.0(rollup@4.52.4))(esbuild@0.25.10)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) vite-plugin-turbosnap: specifier: 1.0.3 version: 1.0.3 vitest: specifier: 3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vitest-fetch-mock: specifier: 0.4.5 - version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) vue-component-type-helpers: specifier: 3.1.1 version: 3.1.1 @@ -1106,14 +1106,14 @@ importers: version: 0.30.19 vite: specifier: 7.1.9 - version: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + version: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) devDependencies: '@types/estree': specifier: 1.0.8 version: 1.0.8 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -1146,7 +1146,7 @@ importers: version: 16.0.0 '@vitejs/plugin-vue': specifier: 6.0.1 - version: 6.0.1(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) + version: 6.0.1(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3)) '@vue/compiler-sfc': specifier: 3.5.22 version: 3.5.22 @@ -1203,7 +1203,7 @@ importers: version: 13.0.0 vite: specifier: 7.1.9 - version: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + version: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vue: specifier: 3.5.22 version: 3.5.22(typescript@5.9.3) @@ -1224,8 +1224,8 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@types/punycode.js': specifier: npm:@types/punycode@2.1.4 version: '@types/punycode@2.1.4' @@ -1243,7 +1243,7 @@ importers: version: 8.46.1(eslint@9.37.0)(typescript@5.9.3) '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@vue/runtime-core': specifier: 3.5.22 version: 3.5.22 @@ -1273,7 +1273,7 @@ importers: version: 4.0.8 msw: specifier: 2.11.5 - version: 2.11.5(@types/node@22.18.10)(typescript@5.9.3) + version: 2.11.5(@types/node@24.9.1)(typescript@5.9.3) nodemon: specifier: 3.1.10 version: 3.1.10 @@ -1309,8 +1309,8 @@ importers: version: 3.5.22(typescript@5.9.3) devDependencies: '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -1355,8 +1355,8 @@ importers: version: 2.0.1 devDependencies: '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@types/wawoff2': specifier: 1.0.2 version: 1.0.2 @@ -1383,8 +1383,8 @@ importers: specifier: 0.20.2 version: 0.20.2 '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 @@ -1424,10 +1424,10 @@ importers: devDependencies: '@microsoft/api-extractor': specifier: 7.53.1 - version: 7.53.1(@types/node@22.18.10) + version: 7.53.1(@types/node@24.9.1) '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -1436,7 +1436,7 @@ importers: version: 8.46.1(eslint@9.37.0)(typescript@5.9.3) '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) esbuild: specifier: 0.25.10 version: 0.25.10 @@ -1460,10 +1460,10 @@ importers: version: 5.9.3 vitest: specifier: 3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vitest-websocket-mock: specifier: 0.5.0 - version: 0.5.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + version: 0.5.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) packages/misskey-js/generator: devDependencies: @@ -1471,8 +1471,8 @@ importers: specifier: 5.0.2 version: 5.0.2(openapi-types@12.1.3) '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -1502,8 +1502,8 @@ importers: version: 1.2.2 devDependencies: '@types/node': - specifier: 22.18.10 - version: 22.18.10 + specifier: 24.9.1 + version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -4565,8 +4565,8 @@ packages: '@types/node@20.19.19': resolution: {integrity: sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==} - '@types/node@22.18.10': - resolution: {integrity: sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==} + '@types/node@24.9.1': + resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} '@types/nodemailer@6.4.20': resolution: {integrity: sha512-uj83z0GqwqMUE6RI4EKptPlav0FYE6vpIlqJAnxzu+/sSezRdbH69rSBCMsdW6DdsCAzoFQZ52c2UIlhRVQYDA==} @@ -10532,6 +10532,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@5.28.5: resolution: {integrity: sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==} engines: {node: '>=14.0'} @@ -12659,31 +12662,31 @@ snapshots: '@inquirer/ansi@1.0.0': {} - '@inquirer/confirm@5.1.18(@types/node@22.18.10)': + '@inquirer/confirm@5.1.18(@types/node@24.9.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.10) - '@inquirer/type': 3.0.8(@types/node@22.18.10) + '@inquirer/core': 10.2.2(@types/node@24.9.1) + '@inquirer/type': 3.0.8(@types/node@24.9.1) optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 - '@inquirer/core@10.2.2(@types/node@22.18.10)': + '@inquirer/core@10.2.2(@types/node@24.9.1)': dependencies: '@inquirer/ansi': 1.0.0 '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.18.10) + '@inquirer/type': 3.0.8(@types/node@24.9.1) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@inquirer/figures@1.0.13': {} - '@inquirer/type@3.0.8(@types/node@22.18.10)': + '@inquirer/type@3.0.8(@types/node@24.9.1)': optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@ioredis/commands@1.4.0': {} @@ -12719,7 +12722,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -12732,14 +12735,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.18.10) + jest-config: 29.7.0(@types/node@24.9.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -12768,7 +12771,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -12786,7 +12789,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -12802,7 +12805,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-regex-util: 30.0.1 '@jest/reporters@29.7.0': @@ -12813,7 +12816,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -12887,7 +12890,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -12897,16 +12900,16 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/yargs': 17.0.33 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.3)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.3)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': dependencies: glob: 10.4.5 magic-string: 0.30.19 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) optionalDependencies: typescript: 5.9.3 @@ -12972,23 +12975,23 @@ snapshots: '@types/react': 18.0.28 react: 19.2.0 - '@microsoft/api-extractor-model@7.31.1(@types/node@22.18.10)': + '@microsoft/api-extractor-model@7.31.1(@types/node@24.9.1)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.17.0(@types/node@22.18.10) + '@rushstack/node-core-library': 5.17.0(@types/node@24.9.1) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.53.1(@types/node@22.18.10)': + '@microsoft/api-extractor@7.53.1(@types/node@24.9.1)': dependencies: - '@microsoft/api-extractor-model': 7.31.1(@types/node@22.18.10) + '@microsoft/api-extractor-model': 7.31.1(@types/node@24.9.1) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.17.0(@types/node@22.18.10) + '@rushstack/node-core-library': 5.17.0(@types/node@24.9.1) '@rushstack/rig-package': 0.6.0 - '@rushstack/terminal': 0.19.1(@types/node@22.18.10) - '@rushstack/ts-command-line': 5.1.1(@types/node@22.18.10) + '@rushstack/terminal': 0.19.1(@types/node@24.9.1) + '@rushstack/ts-command-line': 5.1.1(@types/node@24.9.1) lodash: 4.17.21 minimatch: 10.0.3 resolve: 1.22.10 @@ -13697,7 +13700,7 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.17.0(@types/node@22.18.10)': + '@rushstack/node-core-library@5.17.0(@types/node@24.9.1)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -13708,28 +13711,28 @@ snapshots: resolve: 1.22.10 semver: 7.5.4 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 - '@rushstack/problem-matcher@0.1.1(@types/node@22.18.10)': + '@rushstack/problem-matcher@0.1.1(@types/node@24.9.1)': optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@rushstack/rig-package@0.6.0': dependencies: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.19.1(@types/node@22.18.10)': + '@rushstack/terminal@0.19.1(@types/node@24.9.1)': dependencies: - '@rushstack/node-core-library': 5.17.0(@types/node@22.18.10) - '@rushstack/problem-matcher': 0.1.1(@types/node@22.18.10) + '@rushstack/node-core-library': 5.17.0(@types/node@24.9.1) + '@rushstack/problem-matcher': 0.1.1(@types/node@24.9.1) supports-color: 8.1.1 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 - '@rushstack/ts-command-line@5.1.1(@types/node@22.18.10)': + '@rushstack/ts-command-line@5.1.1(@types/node@24.9.1)': dependencies: - '@rushstack/terminal': 0.19.1(@types/node@22.18.10) + '@rushstack/terminal': 0.19.1(@types/node@24.9.1) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -14322,147 +14325,147 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-actions@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-actions@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.2.2 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) uuid: 9.0.1 - '@storybook/addon-backgrounds@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-backgrounds@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 - '@storybook/addon-controls@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-controls@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 - '@storybook/addon-docs@8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-docs@8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@19.2.0) - '@storybook/blocks': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/csf-plugin': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/react-dom-shim': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/blocks': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/csf-plugin': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/react-dom-shim': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-essentials@8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-essentials@8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - '@storybook/addon-actions': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-backgrounds': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-controls': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-docs': 8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-highlight': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-measure': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-outline': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-toolbars': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/addon-viewport': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@storybook/addon-actions': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-backgrounds': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-controls': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-docs': 8.6.14(@types/react@18.0.28)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-highlight': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-measure': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-outline': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-toolbars': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/addon-viewport': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-highlight@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-highlight@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/addon-interactions@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-interactions@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/test': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/instrumenter': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/test': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) polished: 4.2.2 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 - '@storybook/addon-links@9.1.10(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-links@9.1.10(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) optionalDependencies: react: 19.2.0 - '@storybook/addon-mdx-gfm@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-mdx-gfm@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: remark-gfm: 4.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color - '@storybook/addon-measure@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-measure@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) tiny-invariant: 1.3.3 - '@storybook/addon-outline@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-outline@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 - '@storybook/addon-storysource@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-storysource@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - '@storybook/source-loader': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/source-loader': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) estraverse: 5.3.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) tiny-invariant: 1.3.3 - '@storybook/addon-toolbars@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-toolbars@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/addon-viewport@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/addon-viewport@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: memoizerific: 1.11.3 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/blocks@8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/blocks@8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/icons': 1.2.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 optionalDependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - '@storybook/builder-vite@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': + '@storybook/builder-vite@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': dependencies: - '@storybook/csf-plugin': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@storybook/csf-plugin': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) ts-dedent: 2.2.0 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) - '@storybook/components@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/components@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/core-events@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/core-events@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/csf-plugin@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/csf-plugin@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) unplugin: 1.16.1 - '@storybook/csf-plugin@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/csf-plugin@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) unplugin: 1.16.1 '@storybook/global@5.0.0': {} @@ -14472,106 +14475,106 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - '@storybook/instrumenter@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/instrumenter@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 '@vitest/utils': 2.1.1 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/manager-api@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/manager-api@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/preview-api@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/preview-api@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/react-dom-shim@8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/react-dom-shim@8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/react-dom-shim@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/react-dom-shim@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/react-vite@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.4)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': + '@storybook/react-vite@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.4)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.3)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.3)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@rollup/pluginutils': 5.3.0(rollup@4.52.4) - '@storybook/builder-vite': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/react': 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3) + '@storybook/builder-vite': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@storybook/react': 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3) find-up: 7.0.0 magic-string: 0.30.19 react: 19.2.0 react-docgen: 8.0.1 react-dom: 19.2.0(react@19.2.0) resolve: 1.22.10 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) tsconfig-paths: 4.2.0 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) transitivePeerDependencies: - rollup - supports-color - typescript - '@storybook/react@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)': + '@storybook/react@9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/react-dom-shim': 9.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) optionalDependencies: typescript: 5.9.3 - '@storybook/source-loader@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/source-loader@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: es-toolkit: 1.27.0 estraverse: 5.3.0 prettier: 3.6.2 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/test@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/test@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/instrumenter': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) '@testing-library/dom': 10.4.0 '@testing-library/jest-dom': 6.5.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) '@vitest/expect': 2.0.5 '@vitest/spy': 2.0.5 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/theming@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/theming@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/types@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': + '@storybook/types@8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))': dependencies: - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/vue3-vite@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3))': + '@storybook/vue3-vite@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3))': dependencies: - '@storybook/builder-vite': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) - '@storybook/vue3': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3)) + '@storybook/builder-vite': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@storybook/vue3': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3)) find-package-json: 1.2.0 magic-string: 0.30.19 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) typescript: 5.9.3 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vue-component-meta: 2.2.12(typescript@5.9.3) vue-docgen-api: 4.79.2(vue@3.5.22(typescript@5.9.3)) transitivePeerDependencies: - vue - '@storybook/vue3@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3))': + '@storybook/vue3@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)))(vue@3.5.22(typescript@5.9.3))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + storybook: 9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) type-fest: 2.19.0 vue: 3.5.22(typescript@5.9.3) vue-component-type-helpers: 3.1.1 @@ -14881,7 +14884,7 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/archiver@6.0.3': dependencies: @@ -14917,7 +14920,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/braces@3.0.1': {} @@ -14935,7 +14938,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/content-disposition@0.5.9': {} @@ -14968,7 +14971,7 @@ snapshots: '@types/express-serve-static-core@4.17.33': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 @@ -14981,11 +14984,11 @@ snapshots: '@types/fluent-ffmpeg@2.1.27': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/graceful-fs@4.1.6': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/hammerjs@2.0.46': {} @@ -14999,7 +15002,7 @@ snapshots: '@types/http-link-header@1.0.7': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -15020,7 +15023,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -15058,25 +15061,25 @@ snapshots: '@types/mysql@2.15.27': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/node-fetch@2.6.11': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 form-data: 4.0.4 '@types/node@20.19.19': dependencies: undici-types: 6.21.0 - '@types/node@22.18.10': + '@types/node@24.9.1': dependencies: - undici-types: 6.21.0 + undici-types: 7.16.0 '@types/nodemailer@6.4.20': dependencies: '@aws-sdk/client-ses': 3.873.0 - '@types/node': 22.18.10 + '@types/node': 24.9.1 transitivePeerDependencies: - aws-crt @@ -15089,11 +15092,11 @@ snapshots: '@types/oauth2orize@1.11.5': dependencies: '@types/express': 4.17.17 - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/oauth@0.9.6': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/offscreencanvas@2019.3.0': {} @@ -15105,7 +15108,7 @@ snapshots: '@types/pg@8.15.5': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -15117,7 +15120,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/qs@6.9.7': {} @@ -15135,7 +15138,7 @@ snapshots: '@types/readdir-glob@1.1.1': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/redis-info@3.0.3': {} @@ -15158,7 +15161,7 @@ snapshots: '@types/serve-static@1.15.1': dependencies: '@types/mime': 3.0.1 - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/serviceworker@0.0.74': {} @@ -15184,7 +15187,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 22.18.10 + '@types/node': 24.9.1 form-data: 4.0.4 '@types/supertest@6.0.3': @@ -15194,7 +15197,7 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/throttle-debounce@5.0.2': {} @@ -15210,21 +15213,21 @@ snapshots: '@types/vary@1.1.3': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/wawoff2@1.0.2': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/web-push@3.6.4': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 '@types/yargs-parser@21.0.0': {} @@ -15234,7 +15237,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 optional: true '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3)': @@ -15332,13 +15335,13 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@6.0.1(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))(vue@3.5.22(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) vue: 3.5.22(typescript@5.9.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -15353,7 +15356,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) transitivePeerDependencies: - supports-color @@ -15372,14 +15375,14 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': + '@vitest/mocker@3.2.4(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - msw: 2.11.5(@types/node@22.18.10)(typescript@5.9.3) - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + msw: 2.11.5(@types/node@24.9.1)(typescript@5.9.3) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) '@vitest/pretty-format@2.0.5': dependencies: @@ -16563,13 +16566,13 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.3.0 - create-jest@29.7.0(@types/node@22.18.10): + create-jest@29.7.0(@types/node@24.9.1): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.18.10) + jest-config: 29.7.0(@types/node@24.9.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -18560,7 +18563,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0 @@ -18580,16 +18583,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.18.10): + jest-cli@29.7.0(@types/node@24.9.1): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.18.10) + create-jest: 29.7.0(@types/node@24.9.1) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.18.10) + jest-config: 29.7.0(@types/node@24.9.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -18599,7 +18602,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.18.10): + jest-config@29.7.0(@types/node@24.9.1): dependencies: '@babel/core': 7.28.4 '@jest/test-sequencer': 29.7.0 @@ -18624,7 +18627,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -18653,7 +18656,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -18663,7 +18666,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 22.18.10 + '@types/node': 24.9.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -18702,7 +18705,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -18739,7 +18742,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -18767,7 +18770,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -18813,7 +18816,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -18832,7 +18835,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.18.10 + '@types/node': 24.9.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -18841,17 +18844,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.18.10): + jest@29.7.0(@types/node@24.9.1): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.18.10) + jest-cli: 29.7.0(@types/node@24.9.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -19710,14 +19713,14 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.2 - msw-storybook-addon@2.0.6(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3)): + msw-storybook-addon@2.0.6(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3)): dependencies: is-node-process: 1.2.0 - msw: 2.11.5(@types/node@22.18.10)(typescript@5.9.3) + msw: 2.11.5(@types/node@24.9.1)(typescript@5.9.3) - msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3): + msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.18(@types/node@22.18.10) + '@inquirer/confirm': 5.1.18(@types/node@24.9.1) '@mswjs/interceptors': 0.39.7 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -21501,26 +21504,26 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(eea6f1ff14bf28d064537fe27aa8f490): + storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(8592f997fbe36bffac29bb2d9605c63c): dependencies: - '@storybook/blocks': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/components': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/core-events': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/manager-api': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/preview-api': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/theming': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) - '@storybook/types': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/blocks': 8.6.14(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/components': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/core-events': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/manager-api': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/preview-api': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/theming': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) + '@storybook/types': 8.6.14(storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6))) optionalDependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): + storybook@9.1.10(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@vitest/mocker': 3.2.4(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.10 @@ -22088,6 +22091,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.16.0: {} + undici@5.28.5: dependencies: '@fastify/busboy': 2.1.0 @@ -22227,13 +22232,13 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@3.2.4(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): + vite-node@3.2.4(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@10.2.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) transitivePeerDependencies: - '@types/node' - jiti @@ -22248,16 +22253,16 @@ snapshots: - tsx - yaml - vite-plugin-glsl@1.5.4(@rollup/pluginutils@5.3.0(rollup@4.52.4))(esbuild@0.25.10)(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): + vite-plugin-glsl@1.5.4(@rollup/pluginutils@5.3.0(rollup@4.52.4))(esbuild@0.25.10)(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): dependencies: - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) optionalDependencies: '@rollup/pluginutils': 5.3.0(rollup@4.52.4) esbuild: 0.25.10 vite-plugin-turbosnap@1.0.3: {} - vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): + vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -22266,27 +22271,27 @@ snapshots: rollup: 4.52.4 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 24.9.1 fsevents: 2.3.3 sass: 1.93.2 terser: 5.44.0 tsx: 4.20.6 - vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): + vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): dependencies: - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) - vitest-websocket-mock@0.5.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): + vitest-websocket-mock@0.5.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)): dependencies: '@vitest/utils': 3.2.4 mock-socket: 9.3.1 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.7)(jsdom@26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.11.5(@types/node@22.18.10)(typescript@5.9.3))(vite@7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) + '@vitest/mocker': 3.2.4(msw@2.11.5(@types/node@24.9.1)(typescript@5.9.3))(vite@7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -22304,12 +22309,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.9(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) - vite-node: 3.2.4(@types/node@22.18.10)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite: 7.1.9(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) + vite-node: 3.2.4(@types/node@24.9.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 22.18.10 + '@types/node': 24.9.1 happy-dom: 20.0.7 jsdom: 26.1.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5) transitivePeerDependencies: diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json index 474cbb523e..2074fa45da 100644 --- a/scripts/changelog-checker/package-lock.json +++ b/scripts/changelog-checker/package-lock.json @@ -9,14 +9,14 @@ "version": "1.0.0", "devDependencies": { "@types/mdast": "4.0.4", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@vitest/coverage-v8": "3.2.4", "mdast-util-to-string": "4.0.0", "remark": "15.0.1", "remark-parse": "11.0.0", "typescript": "5.9.3", "unified": "11.0.5", - "vite": "6.3.6", + "vite": "6.4.1", "vite-node": "3.2.4", "vitest": "3.2.4" } @@ -940,14 +940,14 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.18.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", - "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/unist": { @@ -1171,9 +1171,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1247,10 +1247,11 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2728,9 +2729,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -2839,9 +2840,9 @@ } }, "node_modules/vite": { - "version": "6.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", - "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "peer": true, diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json index 30eb1db8ff..9f45f1599a 100644 --- a/scripts/changelog-checker/package.json +++ b/scripts/changelog-checker/package.json @@ -10,14 +10,14 @@ }, "devDependencies": { "@types/mdast": "4.0.4", - "@types/node": "22.18.10", + "@types/node": "24.9.1", "@vitest/coverage-v8": "3.2.4", "mdast-util-to-string": "4.0.0", "remark": "15.0.1", "remark-parse": "11.0.0", "typescript": "5.9.3", "unified": "11.0.5", - "vite": "6.3.6", + "vite": "6.4.1", "vite-node": "3.2.4", "vitest": "3.2.4" } From 6571c87e14a7faf00dfcbff0f17251801d52298b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:35:23 +0900 Subject: [PATCH 08/79] fix: attempt to fix test (#16719) * fix(backend): attempt to fix test * fix * Revert "fix(backend): attempt to fix test" This reverts commit 67dff577c9b23ad0b463b6741872f258170c1615. * attempt to fix test * Revert "fix" This reverts commit cec3d2f5c6418a692cfa673e1e70752260c5d4ba. * fix --- packages/misskey-bubble-game/package.json | 2 ++ packages/misskey-js/package.json | 2 ++ packages/misskey-reversi/package.json | 2 ++ 3 files changed, 6 insertions(+) diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json index 934717571f..acf5254801 100644 --- a/packages/misskey-bubble-game/package.json +++ b/packages/misskey-bubble-game/package.json @@ -6,10 +6,12 @@ "types": "./built/index.d.ts", "exports": { ".": { + "default": "./built/index.js", "import": "./built/index.js", "types": "./built/index.d.ts" }, "./*": { + "default": "./built/*", "import": "./built/*", "types": "./built/*" } diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index acb9e0ea6e..5ac7e0b24a 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -8,10 +8,12 @@ "types": "./built/index.d.ts", "exports": { ".": { + "default": "./built/index.js", "import": "./built/index.js", "types": "./built/index.d.ts" }, "./*": { + "default": "./built/*", "import": "./built/*", "types": "./built/*" } diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json index 6e4a770bb1..36827663b1 100644 --- a/packages/misskey-reversi/package.json +++ b/packages/misskey-reversi/package.json @@ -6,10 +6,12 @@ "types": "./built/index.d.ts", "exports": { ".": { + "default": "./built/index.js", "import": "./built/index.js", "types": "./built/index.d.ts" }, "./*": { + "default": "./built/*", "import": "./built/*", "types": "./built/*" } From a892bbcce511fb0dff943ab23090e1d347594c94 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:58:44 +0900 Subject: [PATCH 09/79] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=8C?= =?UTF-8?q?=E5=8B=95=E3=81=8B=E3=81=AA=E3=81=84=E3=81=9F=E3=82=81nodejs=20?= =?UTF-8?q?24=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E5=8F=96?= =?UTF-8?q?=E3=82=8A=E6=B6=88=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 2 +- .node-version | 2 +- CHANGELOG.md | 2 -- Dockerfile | 2 +- packages/backend/package.json | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e800743469..514abdfb20 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "24.10.0" + "version": "22.15.0" }, "ghcr.io/devcontainers-extra/features/pnpm:2": { "version": "10.10.0" diff --git a/.node-version b/.node-version index 21651351e2..b8ffd70759 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -24.10.0 +22.15.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b08a76093..665f42f5b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ ## Unreleased ### General -- Enhance: Node.js 24.10.0をサポートするようになりました -- Enhance: DockerのNode.jsが24.10.0に更新されました ### Client - Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 diff --git a/Dockerfile b/Dockerfile index 20e24d1dc2..370bed5751 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=24.10.0-bookworm +ARG NODE_VERSION=22.15.0-bookworm # build assets & compile TypeScript diff --git a/packages/backend/package.json b/packages/backend/package.json index dff9fe22cd..afee92fe38 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "engines": { - "node": "^22.15.0 || ^24.10.0" + "node": "^22.15.0" }, "scripts": { "start": "node ./built/boot/entry.js", From a888f2863bb3e7ac14bc63b12810f7a13a98bb70 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:47:04 +0900 Subject: [PATCH 10/79] Node.js 24 support (#16723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "テストが動かないためnodejs 24サポートを取り消し" This reverts commit a892bbcce511fb0dff943ab23090e1d347594c94. * fix jest.js * ファイルの添付方法を変更 --------- Co-authored-by: samunohito <46447427+samunohito@users.noreply.github.com> --- .devcontainer/devcontainer.json | 2 +- .node-version | 2 +- CHANGELOG.md | 2 ++ Dockerfile | 2 +- packages/backend/jest.js | 2 +- packages/backend/package.json | 2 +- packages/backend/test/utils.ts | 8 ++++++-- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 514abdfb20..e800743469 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "22.15.0" + "version": "24.10.0" }, "ghcr.io/devcontainers-extra/features/pnpm:2": { "version": "10.10.0" diff --git a/.node-version b/.node-version index b8ffd70759..21651351e2 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.15.0 +24.10.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 665f42f5b9..4b08a76093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## Unreleased ### General +- Enhance: Node.js 24.10.0をサポートするようになりました +- Enhance: DockerのNode.jsが24.10.0に更新されました ### Client - Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 diff --git a/Dockerfile b/Dockerfile index 370bed5751..20e24d1dc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=22.15.0-bookworm +ARG NODE_VERSION=24.10.0-bookworm # build assets & compile TypeScript diff --git a/packages/backend/jest.js b/packages/backend/jest.js index 0e761d8c92..61f6b00e85 100644 --- a/packages/backend/jest.js +++ b/packages/backend/jest.js @@ -10,7 +10,7 @@ const __dirname = path.dirname(__filename); const args = []; args.push(...[ - ...semver.satisfies(process.version, '^20.17.0 || ^22.0.0') ? ['--no-experimental-require-module'] : [], + ...semver.satisfies(process.version, '^20.17.0 || ^22.0.0 || ^24.10.0') ? ['--no-experimental-require-module'] : [], '--experimental-vm-modules', '--experimental-import-meta-resolve', path.join(__dirname, 'node_modules/jest/bin/jest.js'), diff --git a/packages/backend/package.json b/packages/backend/package.json index afee92fe38..dff9fe22cd 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "engines": { - "node": "^22.15.0" + "node": "^22.15.0 || ^24.10.0" }, "scripts": { "start": "node ./built/boot/entry.js", diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index ace614115c..daae7b9643 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -316,8 +316,12 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO : new URL(path, new URL('resources/', import.meta.url)); const formData = new FormData(); - formData.append('file', blob ?? - new File([new Uint8Array(await readFile(absPath))], basename(absPath.toString()))); + formData.append( + 'file', + blob ?? new Blob([new Uint8Array(await readFile(absPath))]), + basename(absPath.toString()), + ); + formData.append('force', 'true'); if (name) { formData.append('name', name); From 6f76b598a1cf491f5b4fac6ab7a63f57f5c4558c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 1 Nov 2025 13:58:35 +0900 Subject: [PATCH 11/79] fix: follow-up of #16719 [ci skip] (#16732) --- packages/misskey-bubble-game/package.json | 8 ++++---- packages/misskey-js/package.json | 8 ++++---- packages/misskey-reversi/package.json | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json index acf5254801..f9bd4b07aa 100644 --- a/packages/misskey-bubble-game/package.json +++ b/packages/misskey-bubble-game/package.json @@ -6,14 +6,14 @@ "types": "./built/index.d.ts", "exports": { ".": { - "default": "./built/index.js", "import": "./built/index.js", - "types": "./built/index.d.ts" + "types": "./built/index.d.ts", + "default": "./built/index.js" }, "./*": { - "default": "./built/*", "import": "./built/*", - "types": "./built/*" + "types": "./built/*", + "default": "./built/*" } }, "scripts": { diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 5ac7e0b24a..b3f28ddc28 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -8,14 +8,14 @@ "types": "./built/index.d.ts", "exports": { ".": { - "default": "./built/index.js", "import": "./built/index.js", - "types": "./built/index.d.ts" + "types": "./built/index.d.ts", + "default": "./built/index.js" }, "./*": { - "default": "./built/*", "import": "./built/*", - "types": "./built/*" + "types": "./built/*", + "default": "./built/*" } }, "scripts": { diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json index 36827663b1..6d999e8af1 100644 --- a/packages/misskey-reversi/package.json +++ b/packages/misskey-reversi/package.json @@ -6,14 +6,14 @@ "types": "./built/index.d.ts", "exports": { ".": { - "default": "./built/index.js", "import": "./built/index.js", - "types": "./built/index.d.ts" + "types": "./built/index.d.ts", + "default": "./built/index.js" }, "./*": { - "default": "./built/*", "import": "./built/*", - "types": "./built/*" + "types": "./built/*", + "default": "./built/*" } }, "scripts": { From 78ed024b0bb6c49ce5e41480366e24ab4540400a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:12:33 +0900 Subject: [PATCH 12/79] =?UTF-8?q?refactor(frontend):=20EmojiPicker?= =?UTF-8?q?=E3=81=AE=E5=86=97=E9=95=B7=E3=81=AACSS=E3=82=92=E4=B8=80?= =?UTF-8?q?=E9=83=A8=E7=B0=A1=E7=95=A5=E5=8C=96=20(#16717)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/MkEmojiPicker.vue | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 452546375c..33e9137c2f 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -539,46 +539,44 @@ defineExpose({ } &.w1 { - width: calc((var(--eachSize) * 5) + (#{$pad} * 2)); - --columns: 1fr 1fr 1fr 1fr 1fr; + --columns: 5; } &.w2 { - width: calc((var(--eachSize) * 6) + (#{$pad} * 2)); - --columns: 1fr 1fr 1fr 1fr 1fr 1fr; + --columns: 6; } &.w3 { - width: calc((var(--eachSize) * 7) + (#{$pad} * 2)); - --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + --columns: 7; } &.w4 { - width: calc((var(--eachSize) * 8) + (#{$pad} * 2)); - --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + --columns: 8; } &.w5 { - width: calc((var(--eachSize) * 9) + (#{$pad} * 2)); - --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + --columns: 9; } &.h1 { - height: calc((var(--eachSize) * 4) + (#{$pad} * 2)); + --rows: 4; } &.h2 { - height: calc((var(--eachSize) * 6) + (#{$pad} * 2)); + --rows: 6; } &.h3 { - height: calc((var(--eachSize) * 8) + (#{$pad} * 2)); + --rows: 8; } &.h4 { - height: calc((var(--eachSize) * 10) + (#{$pad} * 2)); + --rows: 10; } + width: calc((var(--eachSize) * var(--columns)) + (#{$pad} * 2)); + height: calc((var(--eachSize) * var(--rows)) + (#{$pad} * 2)); + &.asDrawer { width: 100% !important; @@ -593,7 +591,7 @@ defineExpose({ > .body { display: grid; - grid-template-columns: var(--columns); + grid-template-columns: repeat(var(--columns), 1fr); font-size: 30px; > .config { @@ -635,7 +633,7 @@ defineExpose({ ::v-deep(section) { > .body { display: grid; - grid-template-columns: var(--columns); + grid-template-columns: repeat(var(--columns), 1fr); font-size: 30px; > .item { From 6bce19655b95cd5b82f1bff6c2ef49586e496700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:58:29 +0900 Subject: [PATCH 13/79] =?UTF-8?q?fix(frontend):=20=E3=83=89=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=81=AEtip=E3=81=AE=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E4=BF=AE=E6=AD=A3=20(#16742)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkDrive.vue | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 7213e3496d..b84532b40b 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -60,7 +60,9 @@ SPDX-License-Identifier: AGPL-3.0-only @drop.prevent.stop="onDrop" @contextmenu.stop="onContextmenu" > -
+
+
+
{ } } +.tipContainer:not(:empty) { + padding: 16px 32px; +} + .folders, .files { display: grid; @@ -811,6 +817,10 @@ onBeforeUnmount(() => { } @container (max-width: 600px) { + .tipContainer:not(:empty) { + padding: 16px; + } + .folders, .files { padding: 16px; From ca89c864268bf658ae7b451e0a6f0500d0aed050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:12:20 +0900 Subject: [PATCH 14/79] =?UTF-8?q?fix(frontend):=20=E8=87=AA=E5=88=86?= =?UTF-8?q?=E3=81=AB=E5=89=B2=E3=82=8A=E5=BD=93=E3=81=A6=E3=82=89=E3=82=8C?= =?UTF-8?q?=E3=81=9F=E3=83=AD=E3=83=BC=E3=83=AB=E4=B8=80=E8=A6=A7=E3=81=AE?= =?UTF-8?q?=E4=BD=99=E7=99=BD=E3=82=92=E8=AA=BF=E6=95=B4=20(#16747)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/settings/other.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index c4c76884e4..a9af36189d 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -54,7 +54,9 @@ SPDX-License-Identifier: AGPL-3.0-only - +
+ +
From e15b8b7fa37e0272b7eac75ae3002577330050fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:13:24 +0900 Subject: [PATCH 15/79] =?UTF-8?q?fix(frontend):=20formlink=E3=81=AE?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E4=BF=AE=E6=AD=A3=20(#1674?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/form/link.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue index 63cf1815c0..84382607cf 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -47,9 +47,11 @@ defineProps<{ diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue index b34181e5cc..154b3ffc27 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue @@ -345,7 +345,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -380,8 +425,17 @@ function removeLayer(layer: WatermarkPreset['layers'][number]) { .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); + background-image: linear-gradient(135deg, transparent 30%, var(--MI_THEME-panel) 30%, var(--MI_THEME-panel) 50%, transparent 50%, transparent 80%, var(--MI_THEME-panel) 80%, var(--MI_THEME-panel) 100%); + background-size: 20px 20px; +} + +.animatedBg { + animation: bg 1.2s linear infinite; +} + +@keyframes bg { + 0% { background-position: 0 0; } + 100% { background-position: -20px -20px; } } .previewContainer { diff --git a/packages/frontend/src/composables/use-uploader.ts b/packages/frontend/src/composables/use-uploader.ts index e4aa1fda53..8ffb1e656b 100644 --- a/packages/frontend/src/composables/use-uploader.ts +++ b/packages/frontend/src/composables/use-uploader.ts @@ -9,6 +9,8 @@ import isAnimated from 'is-file-animated'; import { EventEmitter } from 'eventemitter3'; import { computed, markRaw, onMounted, onUnmounted, ref, triggerRef } from 'vue'; import type { MenuItem } from '@/types/menu.js'; +import type { WatermarkLayers, WatermarkPreset } from '@/utility/watermark/WatermarkRenderer.js'; +import type { ImageFrameParams, ImageFramePreset } from '@/utility/image-frame-renderer/ImageFrameRenderer.js'; import { genId } from '@/utility/id.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; @@ -16,7 +18,6 @@ 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 { WatermarkRenderer } from '@/utility/watermark.js'; export type UploaderFeatures = { imageEditing?: boolean; @@ -28,13 +29,7 @@ const THUMBNAIL_SUPPORTED_TYPES = [ 'image/png', 'image/webp', 'image/svg+xml', -]; - -const IMAGE_COMPRESSION_SUPPORTED_TYPES = [ - 'image/jpeg', - 'image/png', - 'image/webp', - 'image/svg+xml', + 'image/gif', ]; const IMAGE_EDITING_SUPPORTED_TYPES = [ @@ -49,11 +44,7 @@ const VIDEO_COMPRESSION_SUPPORTED_TYPES = [ // TODO 'video/x-matroska', ]; -const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES; - const IMAGE_PREPROCESS_NEEDED_TYPES = [ - ...WATERMARK_SUPPORTED_TYPES, - ...IMAGE_COMPRESSION_SUPPORTED_TYPES, ...IMAGE_EDITING_SUPPORTED_TYPES, ]; @@ -83,7 +74,9 @@ export type UploaderItem = { compressedSize?: number | null; preprocessedFile?: Blob | null; file: File; - watermarkPresetId: string | null; + watermarkPreset: WatermarkPreset | null; + watermarkLayers: WatermarkLayers | null; + imageFrameParams: ImageFrameParams | null; isSensitive?: boolean; caption?: string | null; abort?: (() => void) | null; @@ -135,6 +128,7 @@ export function useUploader(options: { const id = genId(); const filename = file.name ?? 'untitled'; const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : ''; + const watermarkPreset = uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? (prefer.s.watermarkPresets.find(p => p.id === prefer.s.defaultWatermarkPresetId) ?? null) : null; items.value.push({ id, name: prefer.s.keepOriginalFilename ? filename : id + extension, @@ -146,8 +140,10 @@ export function useUploader(options: { aborted: false, uploaded: null, uploadFailed: false, - compressionLevel: IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultImageCompressionLevel : VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultVideoCompressionLevel : 0, - watermarkPresetId: uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null, + compressionLevel: IMAGE_EDITING_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultImageCompressionLevel : VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultVideoCompressionLevel : 0, + watermarkPreset, + watermarkLayers: watermarkPreset?.layers ?? null, + imageFrameParams: null, file: markRaw(file), }); const reactiveItem = items.value.at(-1)!; @@ -253,7 +249,7 @@ export function useUploader(options: { }, },*/ { icon: 'ti ti-sparkles', - text: i18n.ts._imageEffector.title + ' (BETA)', + text: i18n.ts._imageEffector.title, action: async () => { const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageEffectorDialog.vue').then(x => x.default), { image: item.file, @@ -280,13 +276,14 @@ export function useUploader(options: { if ( uploaderFeatures.value.watermark && $i.policies.watermarkAvailable && - WATERMARK_SUPPORTED_TYPES.includes(item.file.type) && + IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded ) { - function changeWatermarkPreset(presetId: string | null) { - item.watermarkPresetId = presetId; + function change(layers: WatermarkLayers | null, preset?: WatermarkPreset | null) { + item.watermarkPreset = preset ?? null; + item.watermarkLayers = layers; preprocess(item).then(() => { triggerRef(items); }); @@ -295,43 +292,109 @@ export function useUploader(options: { menu.push({ icon: 'ti ti-copyright', text: i18n.ts.watermark, - caption: computed(() => item.watermarkPresetId == null ? null : prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId)?.name), + caption: computed(() => item.watermarkPreset != null ? item.watermarkPreset.name : item.watermarkLayers != null ? i18n.ts.custom : null), type: 'parent', children: [{ - type: 'radioOption', - text: i18n.ts.none, - active: computed(() => item.watermarkPresetId == null), - action: () => changeWatermarkPreset(null), - }, { - type: 'divider', - }, ...prefer.s.watermarkPresets.map(preset => ({ - type: 'radioOption' as const, - text: preset.name, - active: computed(() => item.watermarkPresetId === preset.id), - action: () => changeWatermarkPreset(preset.id), - })), ...(prefer.s.watermarkPresets.length > 0 ? [{ - type: 'divider' as const, - }] : []), { - type: 'button', - icon: 'ti ti-plus', - text: i18n.ts.add, + type: 'button' as const, + icon: 'ti ti-pencil', + text: i18n.ts.edit, action: async () => { const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkWatermarkEditorDialog.vue').then(x => x.default), { + layers: item.watermarkLayers, image: item.file, }, { - ok: (preset) => { - prefer.commit('watermarkPresets', [...prefer.s.watermarkPresets, preset]); - changeWatermarkPreset(preset.id); + ok: (layers) => { + change(layers); }, closed: () => dispose(), }); }, - }], + }, { + type: 'button' as const, + icon: 'ti ti-x', + text: i18n.ts.remove, + action: () => change(null), + }, { + type: 'divider', + }, { + type: 'label', + text: i18n.ts.presets, + }, ...prefer.s.watermarkPresets.map(preset => ({ + type: 'radioOption' as const, + text: preset.name, + active: computed(() => item.watermarkPreset?.id === preset.id), + action: () => change(preset.layers, preset), + }))], }); } if ( - (IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) || VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type)) && + uploaderFeatures.value.imageEditing && + IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) && + !item.preprocessing && + !item.uploading && + !item.uploaded + ) { + function change(params: ImageFrameParams | null) { + item.imageFrameParams = params; + preprocess(item).then(() => { + triggerRef(items); + }); + } + + menu.push({ + icon: 'ti ti-device-ipad-horizontal', + text: i18n.ts.frame, + type: 'parent' as const, + children: [{ + type: 'button' as const, + icon: 'ti ti-pencil', + text: i18n.ts.edit, + action: async () => { + const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), { + params: item.imageFrameParams, + image: item.file, + imageCaption: item.caption ?? null, + imageFilename: item.name, + }, { + ok: (params) => { + change(params); + }, + closed: () => dispose(), + }); + }, + }, ...(item.imageFrameParams != null ? [{ + type: 'button' as const, + icon: 'ti ti-x', + text: i18n.ts.remove, + action: () => change(null), + }] : []), { + type: 'divider' as const, + }, { + type: 'label' as const, + text: i18n.ts.presets, + }, ...prefer.s.imageFramePresets.map(preset => ({ + type: 'button' as const, + text: preset.name, + action: async () => { + const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), { + params: preset.params, + image: item.file, + imageCaption: item.caption ?? null, + imageFilename: item.name, + }, { + ok: (params) => { + change(params); + }, + closed: () => dispose(), + }); + }, + }))], + }); + } + + if ( + (IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) || VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type)) && !item.preprocessing && !item.uploading && !item.uploaded @@ -545,10 +608,10 @@ export function useUploader(options: { let preprocessedFile: Blob | File = item.file; - const needsWatermark = item.watermarkPresetId != null && WATERMARK_SUPPORTED_TYPES.includes(preprocessedFile.type) && $i.policies.watermarkAvailable; - const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId); - if (needsWatermark && preset != null) { + const needsWatermark = item.watermarkLayers != null && IMAGE_EDITING_SUPPORTED_TYPES.includes(preprocessedFile.type) && $i.policies.watermarkAvailable; + if (needsWatermark && item.watermarkLayers != null) { const canvas = window.document.createElement('canvas'); + const WatermarkRenderer = await import('@/utility/watermark/WatermarkRenderer.js').then(x => x.WatermarkRenderer); const renderer = new WatermarkRenderer({ canvas: canvas, renderWidth: imageBitmap.width, @@ -556,9 +619,7 @@ export function useUploader(options: { image: imageBitmap, }); - await renderer.setLayers(preset.layers); - - renderer.render(); + await renderer.render(item.watermarkLayers); preprocessedFile = await new Promise((resolve) => { canvas.toBlob((blob) => { @@ -571,8 +632,35 @@ export function useUploader(options: { }); } + const needsImageFrame = item.imageFrameParams != null && IMAGE_EDITING_SUPPORTED_TYPES.includes(preprocessedFile.type); + if (needsImageFrame && item.imageFrameParams != null) { + const canvas = window.document.createElement('canvas'); + const ExifReader = await import('exifreader'); + const exif = await ExifReader.load(await item.file.arrayBuffer()); + const ImageFrameRenderer = await import('@/utility/image-frame-renderer/ImageFrameRenderer.js').then(x => x.ImageFrameRenderer); + const frameRenderer = new ImageFrameRenderer({ + canvas: canvas, + image: await window.createImageBitmap(preprocessedFile), + exif, + caption: item.caption ?? null, + filename: item.name, + }); + + await frameRenderer.render(item.imageFrameParams); + + preprocessedFile = await new Promise((resolve) => { + canvas.toBlob((blob) => { + if (blob == null) { + throw new Error('Failed to convert canvas to blob'); + } + resolve(blob); + frameRenderer.destroy(); + }, 'image/png'); + }); + } + const compressionSettings = getCompressionSettings(item.compressionLevel); - const needsCompress = item.compressionLevel !== 0 && compressionSettings && IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type) && !(await isAnimated(preprocessedFile)); + const needsCompress = item.compressionLevel !== 0 && compressionSettings && IMAGE_EDITING_SUPPORTED_TYPES.includes(preprocessedFile.type) && !(await isAnimated(preprocessedFile)); if (needsCompress) { const config = { diff --git a/packages/frontend/src/lib/ImageCompositor.ts b/packages/frontend/src/lib/ImageCompositor.ts new file mode 100644 index 0000000000..a26302af77 --- /dev/null +++ b/packages/frontend/src/lib/ImageCompositor.ts @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { createTexture, initShaderProgram } from '../utility/webgl.js'; + +export type ImageCompositorFunctionParams = Record; + +export type ImageCompositorFunction = { + shader: string; + main: (ctx: { + gl: WebGL2RenderingContext; + program: WebGLProgram; + params: PS; + u: Record; + width: number; + height: number; + textures: Map; + }) => void; +}; + +export type ImageCompositorLayer = any> = { + [K in keyof FNS]: { + id: string; + functionId: K; + params: Parameters[0]['params']; + }; +}[keyof FNS]; + +export function defineImageCompositorFunction(fn: ImageCompositorFunction) { + return fn; +} + +// TODO: per layer cache + +export class ImageCompositor>> { + private gl: WebGL2RenderingContext; + private canvas: HTMLCanvasElement | null = null; + private renderWidth: number; + private renderHeight: number; + private baseTexture: WebGLTexture; + private shaderCache: Map = new Map(); + private perLayerResultTextures: Map = new Map(); + private perLayerResultFrameBuffers: Map = new Map(); + private nopProgram: WebGLProgram; + private registeredTextures: Map = new Map(); + private registeredFunctions: Map = new Map(); + + constructor(options: { + canvas: HTMLCanvasElement; + renderWidth: number; + renderHeight: number; + image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement | null; + functions: FNS; + }) { + this.canvas = options.canvas; + this.renderWidth = options.renderWidth; + this.renderHeight = options.renderHeight; + + this.canvas.width = this.renderWidth; + this.canvas.height = this.renderHeight; + + const gl = this.canvas.getContext('webgl2', { + preserveDrawingBuffer: false, + alpha: true, + premultipliedAlpha: false, + }); + + if (gl == null) throw new Error('Failed to initialize WebGL2 context'); + + this.gl = gl; + + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + + const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); + const vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); + + if (options.image != null) { + this.baseTexture = createTexture(gl); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.baseTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, options.image.width, options.image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, options.image); + gl.bindTexture(gl.TEXTURE_2D, null); + } else { + this.baseTexture = createTexture(gl); + gl.activeTexture(gl.TEXTURE0); + } + + this.nopProgram = initShaderProgram(this.gl, `#version 300 es + in vec2 position; + out vec2 in_uv; + + void main() { + in_uv = (position + 1.0) / 2.0; + gl_Position = vec4(position * vec2(1.0, -1.0), 0.0, 1.0); + } + `, `#version 300 es + precision mediump float; + + in vec2 in_uv; + uniform sampler2D u_texture; + out vec4 out_color; + + void main() { + out_color = texture(u_texture, in_uv); + } + `); + + // レジスタ番号はシェーダープログラムに属しているわけではなく、独立の存在なので、とりあえず nopProgram を使って設定する(その後は効果が持続する) + // ref. https://qiita.com/emadurandal/items/5966c8374f03d4de3266 + const positionLocation = gl.getAttribLocation(this.nopProgram, 'position'); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(positionLocation); + + for (const [id, fn] of Object.entries(options.functions)) { + const uniforms = this.extractUniformNamesFromShader(fn.shader); + this.registeredFunctions.set(id, { ...fn, id, uniforms }); + } + } + + private extractUniformNamesFromShader(shader: string): string[] { + const uniformRegex = /uniform\s+\w+\s+(\w+)\s*;/g; + const uniforms: string[] = []; + let match; + while ((match = uniformRegex.exec(shader)) !== null) { + uniforms.push(match[1].replace(/^u_/, '')); + } + return uniforms; + } + + private renderLayer(layer: ImageCompositorLayer, preTexture: WebGLTexture, invert = false) { + const gl = this.gl; + + const fn = this.registeredFunctions.get(layer.functionId); + if (fn == null) return; + + const cachedShader = this.shaderCache.get(fn.id); + const shaderProgram = cachedShader ?? initShaderProgram(this.gl, `#version 300 es + in vec2 position; + uniform bool u_invert; + out vec2 in_uv; + + void main() { + in_uv = (position + 1.0) / 2.0; + gl_Position = u_invert ? vec4(position * vec2(1.0, -1.0), 0.0, 1.0) : vec4(position, 0.0, 1.0); + } + `, fn.shader); + if (cachedShader == null) { + this.shaderCache.set(fn.id, shaderProgram); + } + + gl.useProgram(shaderProgram); + + const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); + gl.uniform2fv(in_resolution, [this.renderWidth, this.renderHeight]); + + const u_invert = gl.getUniformLocation(shaderProgram, 'u_invert'); + gl.uniform1i(u_invert, invert ? 1 : 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, preTexture); + const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture'); + gl.uniform1i(in_texture, 0); + + fn.main({ + gl: gl, + program: shaderProgram, + params: layer.params, + u: Object.fromEntries(fn.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])), + width: this.renderWidth, + height: this.renderHeight, + textures: this.registeredTextures, + }); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + public render(layers: (ImageCompositorLayer)[]) { + const gl = this.gl; + + // 入力をそのまま出力 + if (layers.length === 0) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.baseTexture); + + gl.useProgram(this.nopProgram); + gl.uniform1i(gl.getUniformLocation(this.nopProgram, 'u_texture')!, 0); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + return; + } + + let preTexture = this.baseTexture; + + for (const layer of layers) { + const isLast = layer === layers.at(-1); + + const cachedResultTexture = this.perLayerResultTextures.get(layer.id); + const resultTexture = cachedResultTexture ?? createTexture(gl); + 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); + + if (isLast) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } else { + 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, isLast); + + preTexture = resultTexture; + } + } + + public registerTexture(key: string, image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement) { + const gl = this.gl; + + const existing = this.registeredTextures.get(key); + if (existing != null) { + gl.deleteTexture(existing.texture); + this.registeredTextures.delete(key); + } + + const texture = createTexture(gl); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + 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.registeredTextures.set(key, { + texture: texture, + width: image.width, + height: image.height, + }); + } + + public unregisterTexture(key: string) { + const gl = this.gl; + + const existing = this.registeredTextures.get(key); + if (existing != null) { + gl.deleteTexture(existing.texture); + this.registeredTextures.delete(key); + } + } + + public hasTexture(key: string) { + return this.registeredTextures.has(key); + } + + public getKeysOfRegisteredTextures() { + return this.registeredTextures.keys(); + } + + public changeResolution(width: number, height: number) { + if (this.renderWidth === width && this.renderHeight === height) return; + + this.renderWidth = width; + this.renderHeight = height; + if (this.canvas) { + this.canvas.width = this.renderWidth; + this.canvas.height = this.renderHeight; + } + this.gl.viewport(0, 0, this.renderWidth, this.renderHeight); + } + + /* + * disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意 + */ + public destroy(disposeCanvas = true) { + this.gl.deleteProgram(this.nopProgram); + + for (const shader of this.shaderCache.values()) { + this.gl.deleteProgram(shader); + } + this.shaderCache.clear(); + + for (const texture of this.perLayerResultTextures.values()) { + this.gl.deleteTexture(texture); + } + this.perLayerResultTextures.clear(); + + for (const framebuffer of this.perLayerResultFrameBuffers.values()) { + this.gl.deleteFramebuffer(framebuffer); + } + this.perLayerResultFrameBuffers.clear(); + + for (const texture of this.registeredTextures.values()) { + this.gl.deleteTexture(texture.texture); + } + this.registeredTextures.clear(); + + this.gl.deleteTexture(this.baseTexture); + + if (disposeCanvas) { + const loseContextExt = this.gl.getExtension('WEBGL_lose_context'); + if (loseContextExt) loseContextExt.loseContext(); + } + } +} diff --git a/packages/frontend/src/lib/pizzax.ts b/packages/frontend/src/lib/pizzax.ts index 6dffcf9478..8faac6155c 100644 --- a/packages/frontend/src/lib/pizzax.ts +++ b/packages/frontend/src/lib/pizzax.ts @@ -5,6 +5,8 @@ // PIZZAX --- A lightweight store +// TODO: Misskeyのドメイン知識があるのでutilityなどに移動する + import { onUnmounted, ref, watch } from 'vue'; import { BroadcastChannel } from 'broadcast-channel'; import type { Ref } from 'vue'; diff --git a/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue b/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue new file mode 100644 index 0000000000..62922fc964 --- /dev/null +++ b/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue @@ -0,0 +1,113 @@ + + + + + + + diff --git a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue index bb91d5e212..0c03a4493a 100644 --- a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue +++ b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue @@ -22,8 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index f58ff4c78c..8d443921a9 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -124,6 +124,34 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + +
+
+ + + + + + + + + +
+
+
+
+ { + if (res == null) return; + if (res.enabled) imageFramePresetsSyncEnabled.value = true; + }); + } else { + prefer.disableSync('imageFramePresets'); + imageFramePresetsSyncEnabled.value = false; + } +} + misskeyApi('drive').then(info => { capacity.value = info.capacity; usage.value = info.usage; @@ -266,8 +311,11 @@ function chooseUploadFolder() { async function addWatermarkPreset() { const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkWatermarkEditorDialog.vue').then(x => x.default), { + presetEditMode: true, + preset: null, + layers: [], }, { - ok: (preset: WatermarkPreset) => { + presetOk: (preset) => { prefer.commit('watermarkPresets', [...prefer.s.watermarkPresets, preset]); }, closed: () => dispose(), @@ -299,6 +347,40 @@ function onDeleteWatermarkPreset(id: string) { } } +function onUpdateImageFramePreset(id: string, preset: ImageFramePreset) { + const index = prefer.s.imageFramePresets.findIndex(p => p.id === id); + if (index !== -1) { + prefer.commit('imageFramePresets', [ + ...prefer.s.imageFramePresets.slice(0, index), + preset, + ...prefer.s.imageFramePresets.slice(index + 1), + ]); + } +} + +function onDeleteImageFramePreset(id: string) { + const index = prefer.s.imageFramePresets.findIndex(p => p.id === id); + if (index !== -1) { + prefer.commit('imageFramePresets', [ + ...prefer.s.imageFramePresets.slice(0, index), + ...prefer.s.imageFramePresets.slice(index + 1), + ]); + } +} + +async function addImageFramePreset() { + const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), { + presetEditMode: true, + preset: null, + params: null, + }, { + presetOk: (preset) => { + prefer.commit('imageFramePresets', [...prefer.s.imageFramePresets, preset]); + }, + closed: () => dispose(), + }); +} + function saveProfile() { misskeyApi('i/update', { alwaysMarkNsfw: !!alwaysMarkNsfw.value, diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 915b192605..2f2107d9ed 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -12,7 +12,8 @@ import type { SoundType } from '@/utility/sound.js'; import type { Plugin } from '@/plugin.js'; import type { DeviceKind } from '@/utility/device-kind.js'; import type { DeckProfile } from '@/deck.js'; -import type { WatermarkPreset } from '@/utility/watermark.js'; +import type { WatermarkPreset } from '@/utility/watermark/WatermarkRenderer.js'; +import type { ImageFramePreset } from '@/utility/image-frame-renderer/ImageFrameRenderer.js'; import { genId } from '@/utility/id.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; import { deepEqual } from '@/utility/deep-equal.js'; @@ -437,6 +438,26 @@ export const PREF_DEF = definePreferences({ accountDependent: true, default: null as WatermarkPreset['id'] | null, }, + imageFramePresets: { + accountDependent: true, + default: [] as ImageFramePreset[], + mergeStrategy: (a, b) => { + const mergedItems = [] as typeof a; + for (const x of a.concat(b)) { + const sameIdItem = mergedItems.find(y => y.id === x.id); + if (sameIdItem != null) { + if (deepEqual(x, sameIdItem)) { // 完全な重複は無視 + continue; + } else { // IDは同じなのに内容が違う場合はマージ不可とする + throw new Error(); + } + } else { + mergedItems.push(x); + } + } + return mergedItems; + }, + }, defaultImageCompressionLevel: { default: 2 as 0 | 1 | 2 | 3, }, diff --git a/packages/frontend/src/utility/image-effector/fxs/blockNoise.glsl b/packages/frontend/src/utility/image-compositor-functions/blockNoise.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/blockNoise.glsl rename to packages/frontend/src/utility/image-compositor-functions/blockNoise.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts b/packages/frontend/src/utility/image-compositor-functions/blockNoise.ts similarity index 83% rename from packages/frontend/src/utility/image-effector/fxs/blockNoise.ts rename to packages/frontend/src/utility/image-compositor-functions/blockNoise.ts index 355ab4536c..8c83ef51a0 100644 --- a/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts +++ b/packages/frontend/src/utility/image-compositor-functions/blockNoise.ts @@ -5,14 +5,42 @@ import seedrandom from 'seedrandom'; import shader from './blockNoise.glsl'; -import { defineImageEffectorFx } from '../ImageEffector.js'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_blockNoise = defineImageEffectorFx({ - id: 'blockNoise', - name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.blockNoise, +export const fn = defineImageCompositorFunction<{ + amount: number; + strength: number; + width: number; + height: number; + channelShift: number; + seed: number; +}>({ shader, - uniforms: ['amount', 'channelShift'] as const, + main: ({ gl, program, u, params }) => { + gl.uniform1i(u.amount, params.amount); + gl.uniform1f(u.channelShift, params.channelShift); + + const margin = 0; + + const rnd = seedrandom(params.seed.toString()); + + for (let i = 0; i < params.amount; i++) { + const o = gl.getUniformLocation(program, `u_shiftOrigins[${i.toString()}]`); + gl.uniform2f(o, (rnd() * (1 + (margin * 2))) - margin, (rnd() * (1 + (margin * 2))) - margin); + + const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`); + gl.uniform1f(s, (1 - (rnd() * 2)) * params.strength); + + const sizes = gl.getUniformLocation(program, `u_shiftSizes[${i.toString()}]`); + gl.uniform2f(sizes, params.width, params.height); + } + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.blockNoise, params: { amount: { label: i18n.ts._imageEffector._fxProps.amount, @@ -64,23 +92,4 @@ export const FX_blockNoise = defineImageEffectorFx({ default: 100, }, }, - main: ({ gl, program, u, params }) => { - gl.uniform1i(u.amount, params.amount); - gl.uniform1f(u.channelShift, params.channelShift); - - const margin = 0; - - const rnd = seedrandom(params.seed.toString()); - - for (let i = 0; i < params.amount; i++) { - const o = gl.getUniformLocation(program, `u_shiftOrigins[${i.toString()}]`); - gl.uniform2f(o, (rnd() * (1 + (margin * 2))) - margin, (rnd() * (1 + (margin * 2))) - margin); - - const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`); - gl.uniform1f(s, (1 - (rnd() * 2)) * params.strength); - - const sizes = gl.getUniformLocation(program, `u_shiftSizes[${i.toString()}]`); - gl.uniform2f(sizes, params.width, params.height); - } - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/blur.glsl b/packages/frontend/src/utility/image-compositor-functions/blur.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/blur.glsl rename to packages/frontend/src/utility/image-compositor-functions/blur.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/blur.ts b/packages/frontend/src/utility/image-compositor-functions/blur.ts similarity index 81% rename from packages/frontend/src/utility/image-effector/fxs/blur.ts rename to packages/frontend/src/utility/image-compositor-functions/blur.ts index 40f51fa646..1ab8eee6ba 100644 --- a/packages/frontend/src/utility/image-effector/fxs/blur.ts +++ b/packages/frontend/src/utility/image-compositor-functions/blur.ts @@ -3,15 +3,33 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './blur.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_blur = defineImageEffectorFx({ - id: 'blur', - name: i18n.ts._imageEffector._fxs.blur, +export const fn = defineImageCompositorFunction<{ + offsetX: number; + offsetY: number; + scaleX: number; + scaleY: number; + ellipse: boolean; + angle: number; + radius: number; +}>({ shader, - uniforms: ['offset', 'scale', 'ellipse', 'angle', 'radius', 'samples'] as const, + main: ({ gl, u, params }) => { + gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); + gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); + gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform1f(u.radius, params.radius); + gl.uniform1i(u.samples, 256); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.blur, params: { offsetX: { label: i18n.ts._imageEffector._fxProps.offset + ' X', @@ -72,12 +90,4 @@ export const FX_blur = defineImageEffectorFx({ step: 0.5, }, }, - main: ({ gl, u, params }) => { - gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); - gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); - gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform1f(u.radius, params.radius); - gl.uniform1i(u.samples, 256); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/checker.glsl b/packages/frontend/src/utility/image-compositor-functions/checker.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/checker.glsl rename to packages/frontend/src/utility/image-compositor-functions/checker.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/checker.ts b/packages/frontend/src/utility/image-compositor-functions/checker.ts similarity index 74% rename from packages/frontend/src/utility/image-effector/fxs/checker.ts rename to packages/frontend/src/utility/image-compositor-functions/checker.ts index 7d1938eeb7..e0476bb126 100644 --- a/packages/frontend/src/utility/image-effector/fxs/checker.ts +++ b/packages/frontend/src/utility/image-compositor-functions/checker.ts @@ -3,15 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './checker.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_checker = defineImageEffectorFx({ - id: 'checker', - name: i18n.ts._imageEffector._fxs.checker, +export const fn = defineImageCompositorFunction<{ + angle: number; + scale: number; + color: [number, number, number]; + opacity: number; +}>({ shader, - uniforms: ['angle', 'scale', 'color', 'opacity'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform1f(u.scale, params.scale * params.scale); + gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); + gl.uniform1f(u.opacity, params.opacity); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.checker, params: { angle: { label: i18n.ts._imageEffector._fxProps.angle, @@ -45,10 +58,4 @@ export const FX_checker = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform1f(u.scale, params.scale * params.scale); - gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); - gl.uniform1f(u.opacity, params.opacity); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.glsl b/packages/frontend/src/utility/image-compositor-functions/chromaticAberration.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/chromaticAberration.glsl rename to packages/frontend/src/utility/image-compositor-functions/chromaticAberration.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts b/packages/frontend/src/utility/image-compositor-functions/chromaticAberration.ts similarity index 66% rename from packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts rename to packages/frontend/src/utility/image-compositor-functions/chromaticAberration.ts index ed4d134251..5e327dd6ac 100644 --- a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts +++ b/packages/frontend/src/utility/image-compositor-functions/chromaticAberration.ts @@ -3,15 +3,24 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './chromaticAberration.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_chromaticAberration = defineImageEffectorFx({ - id: 'chromaticAberration', - name: i18n.ts._imageEffector._fxs.chromaticAberration, +export const fn = defineImageCompositorFunction<{ + normalize: boolean; + amount: number; +}>({ shader, - uniforms: ['amount', 'start', 'normalize'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.amount, params.amount); + gl.uniform1i(u.normalize, params.normalize ? 1 : 0); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.chromaticAberration, params: { normalize: { label: i18n.ts._imageEffector._fxProps.normalize, @@ -27,8 +36,4 @@ export const FX_chromaticAberration = defineImageEffectorFx({ step: 0.01, }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.amount, params.amount); - gl.uniform1i(u.normalize, params.normalize ? 1 : 0); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.glsl b/packages/frontend/src/utility/image-compositor-functions/colorAdjust.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/colorAdjust.glsl rename to packages/frontend/src/utility/image-compositor-functions/colorAdjust.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts b/packages/frontend/src/utility/image-compositor-functions/colorAdjust.ts similarity index 79% rename from packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts rename to packages/frontend/src/utility/image-compositor-functions/colorAdjust.ts index 989ca79a2c..33ca05ace7 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts +++ b/packages/frontend/src/utility/image-compositor-functions/colorAdjust.ts @@ -3,15 +3,30 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './colorAdjust.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_colorAdjust = defineImageEffectorFx({ - id: 'colorAdjust', - name: i18n.ts._imageEffector._fxs.colorAdjust, +export const fn = defineImageCompositorFunction<{ + lightness: number; + contrast: number; + hue: number; + brightness: number; + saturation: number; +}>({ shader, - uniforms: ['lightness', 'contrast', 'hue', 'brightness', 'saturation'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.brightness, params.brightness); + gl.uniform1f(u.contrast, params.contrast); + gl.uniform1f(u.hue, params.hue / 2); + gl.uniform1f(u.lightness, params.lightness); + gl.uniform1f(u.saturation, params.saturation); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.colorAdjust, params: { lightness: { label: i18n.ts._imageEffector._fxProps.lightness, @@ -59,11 +74,4 @@ export const FX_colorAdjust = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.brightness, params.brightness); - gl.uniform1f(u.contrast, params.contrast); - gl.uniform1f(u.hue, params.hue / 2); - gl.uniform1f(u.lightness, params.lightness); - gl.uniform1f(u.saturation, params.saturation); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClamp.glsl b/packages/frontend/src/utility/image-compositor-functions/colorClamp.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/colorClamp.glsl rename to packages/frontend/src/utility/image-compositor-functions/colorClamp.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts b/packages/frontend/src/utility/image-compositor-functions/colorClamp.ts similarity index 73% rename from packages/frontend/src/utility/image-effector/fxs/colorClamp.ts rename to packages/frontend/src/utility/image-compositor-functions/colorClamp.ts index f3513011fa..d4e7b786d0 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts +++ b/packages/frontend/src/utility/image-compositor-functions/colorClamp.ts @@ -3,15 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './colorClamp.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_colorClamp = defineImageEffectorFx({ - id: 'colorClamp', - name: i18n.ts._imageEffector._fxs.colorClamp, +export const fn = defineImageCompositorFunction<{ + max: number; + min: number; +}>({ shader, - uniforms: ['rMax', 'rMin', 'gMax', 'gMin', 'bMax', 'bMin'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.rMax, params.max); + gl.uniform1f(u.rMin, 1.0 + params.min); + gl.uniform1f(u.gMax, params.max); + gl.uniform1f(u.gMin, 1.0 + params.min); + gl.uniform1f(u.bMax, params.max); + gl.uniform1f(u.bMin, 1.0 + params.min); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.colorClamp, params: { max: { label: i18n.ts._imageEffector._fxProps.max, @@ -32,12 +45,4 @@ export const FX_colorClamp = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.rMax, params.max); - gl.uniform1f(u.rMin, 1.0 + params.min); - gl.uniform1f(u.gMax, params.max); - gl.uniform1f(u.gMin, 1.0 + params.min); - gl.uniform1f(u.bMax, params.max); - gl.uniform1f(u.bMin, 1.0 + params.min); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts b/packages/frontend/src/utility/image-compositor-functions/colorClampAdvanced.ts similarity index 83% rename from packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts rename to packages/frontend/src/utility/image-compositor-functions/colorClampAdvanced.ts index 397e16c1ba..492524ec06 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts +++ b/packages/frontend/src/utility/image-compositor-functions/colorClampAdvanced.ts @@ -3,15 +3,32 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './colorClamp.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_colorClampAdvanced = defineImageEffectorFx({ - id: 'colorClampAdvanced', - name: i18n.ts._imageEffector._fxs.colorClampAdvanced, +export const fn = defineImageCompositorFunction<{ + rMax: number; + rMin: number; + gMax: number; + gMin: number; + bMax: number; + bMin: number; +}>({ shader, - uniforms: ['rMax', 'rMin', 'gMax', 'gMin', 'bMax', 'bMin'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.rMax, params.rMax); + gl.uniform1f(u.rMin, 1.0 + params.rMin); + gl.uniform1f(u.gMax, params.gMax); + gl.uniform1f(u.gMin, 1.0 + params.gMin); + gl.uniform1f(u.bMax, params.bMax); + gl.uniform1f(u.bMin, 1.0 + params.bMin); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.colorClampAdvanced, params: { rMax: { label: `${i18n.ts._imageEffector._fxProps.max} (${i18n.ts._imageEffector._fxProps.redComponent})`, @@ -68,12 +85,4 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.rMax, params.rMax); - gl.uniform1f(u.rMin, 1.0 + params.rMin); - gl.uniform1f(u.gMax, params.gMax); - gl.uniform1f(u.gMin, 1.0 + params.gMin); - gl.uniform1f(u.bMax, params.bMax); - gl.uniform1f(u.bMin, 1.0 + params.bMin); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/distort.glsl b/packages/frontend/src/utility/image-compositor-functions/distort.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/distort.glsl rename to packages/frontend/src/utility/image-compositor-functions/distort.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/distort.ts b/packages/frontend/src/utility/image-compositor-functions/distort.ts similarity index 76% rename from packages/frontend/src/utility/image-effector/fxs/distort.ts rename to packages/frontend/src/utility/image-compositor-functions/distort.ts index 3ea93a0266..bd0fcdf42f 100644 --- a/packages/frontend/src/utility/image-effector/fxs/distort.ts +++ b/packages/frontend/src/utility/image-compositor-functions/distort.ts @@ -3,15 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './distort.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_distort = defineImageEffectorFx({ - id: 'distort', - name: i18n.ts._imageEffector._fxs.distort, +export const fn = defineImageCompositorFunction<{ + direction: number; + phase: number; + frequency: number; + strength: number; +}>({ shader, - uniforms: ['phase', 'frequency', 'strength', 'direction'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.phase, params.phase); + gl.uniform1f(u.frequency, params.frequency); + gl.uniform1f(u.strength, params.strength); + gl.uniform1i(u.direction, params.direction); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.distort, params: { direction: { label: i18n.ts._imageEffector._fxProps.direction, @@ -49,10 +62,4 @@ export const FX_distort = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.phase, params.phase); - gl.uniform1f(u.frequency, params.frequency); - gl.uniform1f(u.strength, params.strength); - gl.uniform1i(u.direction, params.direction); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/fill.glsl b/packages/frontend/src/utility/image-compositor-functions/fill.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/fill.glsl rename to packages/frontend/src/utility/image-compositor-functions/fill.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/fill.ts b/packages/frontend/src/utility/image-compositor-functions/fill.ts similarity index 81% rename from packages/frontend/src/utility/image-effector/fxs/fill.ts rename to packages/frontend/src/utility/image-compositor-functions/fill.ts index 772cd76cf7..901bdadfe5 100644 --- a/packages/frontend/src/utility/image-effector/fxs/fill.ts +++ b/packages/frontend/src/utility/image-compositor-functions/fill.ts @@ -3,15 +3,34 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './fill.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_fill = defineImageEffectorFx({ - id: 'fill', - name: i18n.ts._imageEffector._fxs.fill, +export const fn = defineImageCompositorFunction<{ + offsetX: number; + offsetY: number; + scaleX: number; + scaleY: number; + ellipse: boolean; + angle: number; + color: [number, number, number]; + opacity: number; +}>({ shader, - uniforms: ['offset', 'scale', 'ellipse', 'angle', 'color', 'opacity'] as const, + main: ({ gl, u, params }) => { + gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); + gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); + gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); + gl.uniform1f(u.opacity, params.opacity); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.fill, params: { offsetX: { label: i18n.ts._imageEffector._fxProps.offset + ' X', @@ -78,12 +97,4 @@ export const FX_fill = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); - gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); - gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); - gl.uniform1f(u.opacity, params.opacity); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/grayscale.glsl b/packages/frontend/src/utility/image-compositor-functions/grayscale.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/grayscale.glsl rename to packages/frontend/src/utility/image-compositor-functions/grayscale.glsl diff --git a/packages/frontend/src/utility/image-compositor-functions/grayscale.ts b/packages/frontend/src/utility/image-compositor-functions/grayscale.ts new file mode 100644 index 0000000000..b6860de0a2 --- /dev/null +++ b/packages/frontend/src/utility/image-compositor-functions/grayscale.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import shader from './grayscale.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; +import { i18n } from '@/i18n.js'; + +export const fn = defineImageCompositorFunction({ + shader, + main: ({ gl, u, params }) => { + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.grayscale, + params: { + }, +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/invert.glsl b/packages/frontend/src/utility/image-compositor-functions/invert.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/invert.glsl rename to packages/frontend/src/utility/image-compositor-functions/invert.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/invert.ts b/packages/frontend/src/utility/image-compositor-functions/invert.ts similarity index 68% rename from packages/frontend/src/utility/image-effector/fxs/invert.ts rename to packages/frontend/src/utility/image-compositor-functions/invert.ts index 9417047931..f64e68034e 100644 --- a/packages/frontend/src/utility/image-effector/fxs/invert.ts +++ b/packages/frontend/src/utility/image-compositor-functions/invert.ts @@ -3,15 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './invert.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_invert = defineImageEffectorFx({ - id: 'invert', - name: i18n.ts._imageEffector._fxs.invert, +export const fn = defineImageCompositorFunction<{ + r: boolean; + g: boolean; + b: boolean; +}>({ shader, - uniforms: ['r', 'g', 'b'] as const, + main: ({ gl, u, params }) => { + gl.uniform1i(u.r, params.r ? 1 : 0); + gl.uniform1i(u.g, params.g ? 1 : 0); + gl.uniform1i(u.b, params.b ? 1 : 0); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.invert, params: { r: { label: i18n.ts._imageEffector._fxProps.redComponent, @@ -29,9 +40,4 @@ export const FX_invert = defineImageEffectorFx({ default: true, }, }, - main: ({ gl, u, params }) => { - gl.uniform1i(u.r, params.r ? 1 : 0); - gl.uniform1i(u.g, params.g ? 1 : 0); - gl.uniform1i(u.b, params.b ? 1 : 0); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/mirror.glsl b/packages/frontend/src/utility/image-compositor-functions/mirror.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/mirror.glsl rename to packages/frontend/src/utility/image-compositor-functions/mirror.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/mirror.ts b/packages/frontend/src/utility/image-compositor-functions/mirror.ts similarity index 63% rename from packages/frontend/src/utility/image-effector/fxs/mirror.ts rename to packages/frontend/src/utility/image-compositor-functions/mirror.ts index 6515454ead..47d19c0553 100644 --- a/packages/frontend/src/utility/image-effector/fxs/mirror.ts +++ b/packages/frontend/src/utility/image-compositor-functions/mirror.ts @@ -3,15 +3,24 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './mirror.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_mirror = defineImageEffectorFx({ - id: 'mirror', - name: i18n.ts._imageEffector._fxs.mirror, +export const fn = defineImageCompositorFunction<{ + h: number; + v: number; +}>({ shader, - uniforms: ['h', 'v'] as const, + main: ({ gl, u, params }) => { + gl.uniform1i(u.h, params.h); + gl.uniform1i(u.v, params.v); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.mirror, params: { h: { label: i18n.ts.horizontal, @@ -19,7 +28,7 @@ export const FX_mirror = defineImageEffectorFx({ enum: [ { value: -1 as const, icon: 'ti ti-arrow-bar-right' }, { value: 0 as const, icon: 'ti ti-minus-vertical' }, - { value: 1 as const, icon: 'ti ti-arrow-bar-left' } + { value: 1 as const, icon: 'ti ti-arrow-bar-left' }, ], default: -1, }, @@ -29,13 +38,9 @@ export const FX_mirror = defineImageEffectorFx({ enum: [ { value: -1 as const, icon: 'ti ti-arrow-bar-down' }, { value: 0 as const, icon: 'ti ti-minus' }, - { value: 1 as const, icon: 'ti ti-arrow-bar-up' } + { value: 1 as const, icon: 'ti ti-arrow-bar-up' }, ], default: 0, }, }, - main: ({ gl, u, params }) => { - gl.uniform1i(u.h, params.h); - gl.uniform1i(u.v, params.v); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/pixelate.glsl b/packages/frontend/src/utility/image-compositor-functions/pixelate.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/pixelate.glsl rename to packages/frontend/src/utility/image-compositor-functions/pixelate.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/pixelate.ts b/packages/frontend/src/utility/image-compositor-functions/pixelate.ts similarity index 81% rename from packages/frontend/src/utility/image-effector/fxs/pixelate.ts rename to packages/frontend/src/utility/image-compositor-functions/pixelate.ts index e3eef49b23..249d272e7e 100644 --- a/packages/frontend/src/utility/image-effector/fxs/pixelate.ts +++ b/packages/frontend/src/utility/image-compositor-functions/pixelate.ts @@ -3,15 +3,33 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './pixelate.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_pixelate = defineImageEffectorFx({ - id: 'pixelate', - name: i18n.ts._imageEffector._fxs.pixelate, +export const fn = defineImageCompositorFunction<{ + offsetX: number; + offsetY: number; + scaleX: number; + scaleY: number; + ellipse: boolean; + angle: number; + strength: number; +}>({ shader, - uniforms: ['offset', 'scale', 'ellipse', 'angle', 'strength', 'samples'] as const, + main: ({ gl, u, params }) => { + gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); + gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); + gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform1f(u.strength, params.strength * params.strength); + gl.uniform1i(u.samples, 256); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.pixelate, params: { offsetX: { label: i18n.ts._imageEffector._fxProps.offset + ' X', @@ -72,12 +90,4 @@ export const FX_pixelate = defineImageEffectorFx({ step: 0.01, }, }, - main: ({ gl, u, params }) => { - gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2); - gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2); - gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0); - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform1f(u.strength, params.strength * params.strength); - gl.uniform1i(u.samples, 256); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/polkadot.glsl b/packages/frontend/src/utility/image-compositor-functions/polkadot.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/polkadot.glsl rename to packages/frontend/src/utility/image-compositor-functions/polkadot.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/polkadot.ts b/packages/frontend/src/utility/image-compositor-functions/polkadot.ts similarity index 81% rename from packages/frontend/src/utility/image-effector/fxs/polkadot.ts rename to packages/frontend/src/utility/image-compositor-functions/polkadot.ts index 521e08cc7b..d94d704be3 100644 --- a/packages/frontend/src/utility/image-effector/fxs/polkadot.ts +++ b/packages/frontend/src/utility/image-compositor-functions/polkadot.ts @@ -3,16 +3,36 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './polkadot.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -// Primarily used for watermark -export const FX_polkadot = defineImageEffectorFx({ - id: 'polkadot', - name: i18n.ts._imageEffector._fxs.polkadot, +export const fn = defineImageCompositorFunction<{ + angle: number; + scale: number; + majorRadius: number; + majorOpacity: number; + minorDivisions: number; + minorRadius: number; + minorOpacity: number; + color: [number, number, number]; +}>({ shader, - uniforms: ['angle', 'scale', 'major_radius', 'major_opacity', 'minor_divisions', 'minor_radius', 'minor_opacity', 'color'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform1f(u.scale, params.scale * params.scale); + gl.uniform1f(u.major_radius, params.majorRadius); + gl.uniform1f(u.major_opacity, params.majorOpacity); + gl.uniform1f(u.minor_divisions, params.minorDivisions); + gl.uniform1f(u.minor_radius, params.minorRadius); + gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); + gl.uniform1f(u.minor_opacity, params.minorOpacity); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.polkadot, params: { angle: { label: i18n.ts._imageEffector._fxProps.angle, @@ -79,14 +99,4 @@ export const FX_polkadot = defineImageEffectorFx({ default: [1, 1, 1], }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform1f(u.scale, params.scale * params.scale); - gl.uniform1f(u.major_radius, params.majorRadius); - gl.uniform1f(u.major_opacity, params.majorOpacity); - gl.uniform1f(u.minor_divisions, params.minorDivisions); - gl.uniform1f(u.minor_radius, params.minorRadius); - gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); - gl.uniform1f(u.minor_opacity, params.minorOpacity); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/stripe.glsl b/packages/frontend/src/utility/image-compositor-functions/stripe.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/stripe.glsl rename to packages/frontend/src/utility/image-compositor-functions/stripe.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/stripe.ts b/packages/frontend/src/utility/image-compositor-functions/stripe.ts similarity index 77% rename from packages/frontend/src/utility/image-effector/fxs/stripe.ts rename to packages/frontend/src/utility/image-compositor-functions/stripe.ts index 3a6ecf970c..d429a124bc 100644 --- a/packages/frontend/src/utility/image-effector/fxs/stripe.ts +++ b/packages/frontend/src/utility/image-compositor-functions/stripe.ts @@ -3,16 +3,31 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './stripe.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -// Primarily used for watermark -export const FX_stripe = defineImageEffectorFx({ - id: 'stripe', - name: i18n.ts._imageEffector._fxs.stripe, +export const fn = defineImageCompositorFunction<{ + angle: number; + frequency: number; + threshold: number; + color: [number, number, number]; + opacity: number; +}>({ shader, - uniforms: ['angle', 'frequency', 'phase', 'threshold', 'color', 'opacity'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.angle, params.angle / 2); + gl.uniform1f(u.frequency, params.frequency * params.frequency); + gl.uniform1f(u.phase, 0.0); + gl.uniform1f(u.threshold, params.threshold); + gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); + gl.uniform1f(u.opacity, params.opacity); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.stripe, params: { angle: { label: i18n.ts._imageEffector._fxProps.angle, @@ -55,12 +70,4 @@ export const FX_stripe = defineImageEffectorFx({ toViewValue: v => Math.round(v * 100) + '%', }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.angle, params.angle / 2); - gl.uniform1f(u.frequency, params.frequency * params.frequency); - gl.uniform1f(u.phase, 0.0); - gl.uniform1f(u.threshold, params.threshold); - gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]); - gl.uniform1f(u.opacity, params.opacity); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/tearing.glsl b/packages/frontend/src/utility/image-compositor-functions/tearing.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/tearing.glsl rename to packages/frontend/src/utility/image-compositor-functions/tearing.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/tearing.ts b/packages/frontend/src/utility/image-compositor-functions/tearing.ts similarity index 82% rename from packages/frontend/src/utility/image-effector/fxs/tearing.ts rename to packages/frontend/src/utility/image-compositor-functions/tearing.ts index 453b16bb19..66c61b7ca8 100644 --- a/packages/frontend/src/utility/image-effector/fxs/tearing.ts +++ b/packages/frontend/src/utility/image-compositor-functions/tearing.ts @@ -5,14 +5,39 @@ import seedrandom from 'seedrandom'; import shader from './tearing.glsl'; -import { defineImageEffectorFx } from '../ImageEffector.js'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_tearing = defineImageEffectorFx({ - id: 'tearing', - name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.tearing, +export const fn = defineImageCompositorFunction<{ + amount: number; + strength: number; + size: number; + channelShift: number; + seed: number; +}>({ shader, - uniforms: ['amount', 'channelShift'] as const, + main: ({ gl, program, u, params }) => { + gl.uniform1i(u.amount, params.amount); + 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); + + const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`); + gl.uniform1f(h, rnd() * params.size); + } + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.tearing, params: { amount: { label: i18n.ts._imageEffector._fxProps.amount, @@ -55,21 +80,4 @@ export const FX_tearing = defineImageEffectorFx({ default: 100, }, }, - main: ({ gl, program, u, params }) => { - gl.uniform1i(u.amount, params.amount); - 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); - - const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`); - gl.uniform1f(h, rnd() * params.size); - } - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/threshold.glsl b/packages/frontend/src/utility/image-compositor-functions/threshold.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/threshold.glsl rename to packages/frontend/src/utility/image-compositor-functions/threshold.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/threshold.ts b/packages/frontend/src/utility/image-compositor-functions/threshold.ts similarity index 71% rename from packages/frontend/src/utility/image-effector/fxs/threshold.ts rename to packages/frontend/src/utility/image-compositor-functions/threshold.ts index d0bb8305ae..83ea788771 100644 --- a/packages/frontend/src/utility/image-effector/fxs/threshold.ts +++ b/packages/frontend/src/utility/image-compositor-functions/threshold.ts @@ -3,15 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './threshold.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_threshold = defineImageEffectorFx({ - id: 'threshold', - name: i18n.ts._imageEffector._fxs.threshold, +export const fn = defineImageCompositorFunction<{ + r: number; + g: number; + b: number; +}>({ shader, - uniforms: ['r', 'g', 'b'] as const, + main: ({ gl, u, params }) => { + gl.uniform1f(u.r, params.r); + gl.uniform1f(u.g, params.g); + gl.uniform1f(u.b, params.b); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.threshold, params: { r: { label: i18n.ts._imageEffector._fxProps.redComponent, @@ -38,9 +49,4 @@ export const FX_threshold = defineImageEffectorFx({ step: 0.01, }, }, - main: ({ gl, u, params }) => { - gl.uniform1f(u.r, params.r); - gl.uniform1f(u.g, params.g); - gl.uniform1f(u.b, params.b); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/fxs/zoomLines.glsl b/packages/frontend/src/utility/image-compositor-functions/zoomLines.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/zoomLines.glsl rename to packages/frontend/src/utility/image-compositor-functions/zoomLines.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts b/packages/frontend/src/utility/image-compositor-functions/zoomLines.ts similarity index 80% rename from packages/frontend/src/utility/image-effector/fxs/zoomLines.ts rename to packages/frontend/src/utility/image-compositor-functions/zoomLines.ts index 8c0956d24e..f8768e4ec3 100644 --- a/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts +++ b/packages/frontend/src/utility/image-compositor-functions/zoomLines.ts @@ -3,15 +3,34 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; import shader from './zoomLines.glsl'; +import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; import { i18n } from '@/i18n.js'; -export const FX_zoomLines = defineImageEffectorFx({ - id: 'zoomLines', - name: i18n.ts._imageEffector._fxs.zoomLines, +export const fn = defineImageCompositorFunction<{ + x: number; + y: number; + frequency: number; + smoothing: boolean; + threshold: number; + maskSize: number; + black: boolean; +}>({ shader, - uniforms: ['pos', 'frequency', 'thresholdEnabled', 'threshold', 'maskSize', 'black'] as const, + main: ({ gl, u, params }) => { + gl.uniform2f(u.pos, params.x / 2, params.y / 2); + gl.uniform1f(u.frequency, params.frequency * params.frequency); + // thresholdの調整が有効な間はsmoothingが利用できない + gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1); + gl.uniform1f(u.threshold, params.threshold); + gl.uniform1f(u.maskSize, params.maskSize); + gl.uniform1i(u.black, params.black ? 1 : 0); + }, +}); + +export const uiDefinition = { + name: i18n.ts._imageEffector._fxs.zoomLines, params: { x: { label: i18n.ts._imageEffector._fxProps.centerX, @@ -65,13 +84,4 @@ export const FX_zoomLines = defineImageEffectorFx({ default: false, }, }, - main: ({ gl, u, params }) => { - gl.uniform2f(u.pos, params.x / 2, params.y / 2); - gl.uniform1f(u.frequency, params.frequency * params.frequency); - // thresholdの調整が有効な間はsmoothingが利用できない - gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1); - gl.uniform1f(u.threshold, params.threshold); - gl.uniform1f(u.maskSize, params.maskSize); - gl.uniform1i(u.black, params.black ? 1 : 0); - }, -}); +} satisfies ImageEffectorUiDefinition; diff --git a/packages/frontend/src/utility/image-effector/ImageEffector.ts b/packages/frontend/src/utility/image-effector/ImageEffector.ts index 26c74bfae5..b4295c4637 100644 --- a/packages/frontend/src/utility/image-effector/ImageEffector.ts +++ b/packages/frontend/src/utility/image-effector/ImageEffector.ts @@ -3,18 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import QRCodeStyling from 'qr-code-styling'; -import { url, host } from '@@/js/config.js'; -import { getProxiedImageUrl } from '../media-proxy.js'; -import { initShaderProgram } from '../webgl.js'; -import { ensureSignin } from '@/i.js'; +import { FXS } from './fxs.js'; +import type { ImageCompositorFunction, ImageCompositorLayer } from '@/lib/ImageCompositor.js'; +import { ImageCompositor } from '@/lib/ImageCompositor.js'; export type ImageEffectorRGB = [r: number, g: number, b: number]; -type ParamTypeToPrimitive = { - [K in ImageEffectorFxParamDef['type']]: (ImageEffectorFxParamDef & { type: K })['default']; -}; - interface CommonParamDef { type: string; label?: string; @@ -60,479 +54,77 @@ interface SeedParamDef extends CommonParamDef { default: number; }; -interface TextureParamDef extends CommonParamDef { - type: 'texture'; - default: { - type: 'text'; text: string | null; - } | { - type: 'url'; url: string | null; - } | { - type: 'qr'; data: string | null; - } | null; -}; - interface ColorParamDef extends CommonParamDef { type: 'color'; default: ImageEffectorRGB; }; -type ImageEffectorFxParamDef = NumberParamDef | NumberEnumParamDef | BooleanParamDef | AlignParamDef | SeedParamDef | TextureParamDef | ColorParamDef; +type ImageEffectorFxParamDef = NumberParamDef | NumberEnumParamDef | BooleanParamDef | AlignParamDef | SeedParamDef | ColorParamDef; export type ImageEffectorFxParamDefs = Record; -export type GetParamType = - T extends NumberEnumParamDef - ? T['enum'][number]['value'] - : ParamTypeToPrimitive[T['type']]; - -export type ParamsRecordTypeToDefRecord = { - [K in keyof PS]: GetParamType; -}; - -export function defineImageEffectorFx(fx: ImageEffectorFx) { - return fx; -} - -export type ImageEffectorFx = { - id: ID; - name: string; - shader: string; - uniforms: US; - params: PS, - main: (ctx: { - gl: WebGL2RenderingContext; - program: WebGLProgram; - params: ParamsRecordTypeToDefRecord; - u: Record; - width: number; - height: number; - textures: Record; - }) => void; -}; - export type ImageEffectorLayer = { - id: string; - fxId: string; - params: Record; + [K in keyof typeof FXS]: { + id: string; + fxId: K; + params: Parameters<(typeof FXS)[K]['fn']['main']>[0]['params']; + }; +}[keyof typeof FXS]; + +export type ImageEffectorUiDefinition = ImageCompositorFunction> = { + name: string; + params: Fn extends ImageCompositorFunction ? { + [K in keyof P]: ImageEffectorFxParamDef; + } : never; }; -function getValue(params: Record, k: string): ParamTypeToPrimitive[T] { - return params[k]; -} +type ImageEffectorImageCompositor = ImageCompositor<{ + [K in keyof typeof FXS]: typeof FXS[K]['fn']; +}>; -export class ImageEffector>> { - private gl: WebGL2RenderingContext; +export class ImageEffector { private canvas: HTMLCanvasElement | null = null; - private renderWidth: number; - private renderHeight: number; - private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement; - private layers: ImageEffectorLayer[] = []; - private originalImageTexture: WebGLTexture; - private shaderCache: Map = new Map(); - private perLayerResultTextures: Map = new Map(); - private perLayerResultFrameBuffers: Map = new Map(); - private nopProgram: WebGLProgram; - private fxs: [...IEX]; - private paramTextures: Map = new Map(); + private compositor: ImageEffectorImageCompositor; constructor(options: { canvas: HTMLCanvasElement; renderWidth: number; renderHeight: number; - image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement; - fxs: [...IEX]; + image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement | null; }) { this.canvas = options.canvas; - this.renderWidth = options.renderWidth; - this.renderHeight = options.renderHeight; - this.originalImage = options.image; - this.fxs = options.fxs; - this.canvas.width = this.renderWidth; - this.canvas.height = this.renderHeight; - - const gl = this.canvas.getContext('webgl2', { - preserveDrawingBuffer: false, - alpha: true, - premultipliedAlpha: false, + this.compositor = new ImageCompositor({ + canvas: this.canvas, + renderWidth: options.renderWidth, + renderHeight: options.renderHeight, + image: options.image, + functions: Object.fromEntries(Object.entries(FXS).map(([fxId, fx]) => [fxId, fx.fn])), }); - - if (gl == null) { - throw new Error('Failed to initialize WebGL2 context'); - } - - this.gl = gl; - - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - - const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); - const vertexBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); - - this.originalImageTexture = createTexture(gl); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.originalImage.width, this.originalImage.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage); - gl.bindTexture(gl.TEXTURE_2D, null); - - this.nopProgram = initShaderProgram(this.gl, `#version 300 es - in vec2 position; - out vec2 in_uv; - - void main() { - in_uv = (position + 1.0) / 2.0; - gl_Position = vec4(position * vec2(1.0, -1.0), 0.0, 1.0); - } - `, `#version 300 es - precision mediump float; - - in vec2 in_uv; - uniform sampler2D u_texture; - out vec4 out_color; - - void main() { - out_color = texture(u_texture, in_uv); - } - `); - - // レジスタ番号はシェーダープログラムに属しているわけではなく、独立の存在なので、とりあえず nopProgram を使って設定する(その後は効果が持続する) - // ref. https://qiita.com/emadurandal/items/5966c8374f03d4de3266 - const positionLocation = gl.getAttribLocation(this.nopProgram, 'position'); - gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(positionLocation); } - private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture, invert = false) { - const gl = this.gl; - - const fx = this.fxs.find(fx => fx.id === layer.fxId); - if (fx == null) return; - - const cachedShader = this.shaderCache.get(fx.id); - const shaderProgram = cachedShader ?? initShaderProgram(this.gl, `#version 300 es - in vec2 position; - uniform bool u_invert; - out vec2 in_uv; - - void main() { - in_uv = (position + 1.0) / 2.0; - gl_Position = u_invert ? vec4(position * vec2(1.0, -1.0), 0.0, 1.0) : vec4(position, 0.0, 1.0); - } - `, fx.shader); - if (cachedShader == null) { - this.shaderCache.set(fx.id, shaderProgram); - } - - gl.useProgram(shaderProgram); - - const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); - gl.uniform2fv(in_resolution, [this.renderWidth, this.renderHeight]); - - const u_invert = gl.getUniformLocation(shaderProgram, 'u_invert'); - gl.uniform1i(u_invert, invert ? 1 : 0); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, preTexture); - const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture'); - gl.uniform1i(in_texture, 0); - - fx.main({ - gl: gl, - program: shaderProgram, - params: Object.fromEntries( - Object.entries(fx.params as ImageEffectorFxParamDefs).map(([key, param]) => { - return [key, layer.params[key] ?? param.default]; - }), - ), - u: Object.fromEntries(fx.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])), - width: this.renderWidth, - height: this.renderHeight, - textures: Object.fromEntries( - Object.entries(fx.params as ImageEffectorFxParamDefs).map(([k, v]) => { - if (v.type !== 'texture') return [k, null]; - const param = getValue(layer.params, k); - if (param == null) return [k, null]; - const texture = this.paramTextures.get(this.getTextureKeyForParam(param)) ?? null; - return [k, texture]; - })), - }); - - gl.drawArrays(gl.TRIANGLES, 0, 6); - } - - public render() { - const gl = this.gl; - - // 入力をそのまま出力 - if (this.layers.length === 0) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); - - gl.useProgram(this.nopProgram); - gl.uniform1i(gl.getUniformLocation(this.nopProgram, 'u_texture')!, 0); - - gl.drawArrays(gl.TRIANGLES, 0, 6); - return; - } - - let preTexture = this.originalImageTexture; - - for (const layer of this.layers) { - const isLast = layer === this.layers.at(-1); - - const cachedResultTexture = this.perLayerResultTextures.get(layer.id); - const resultTexture = cachedResultTexture ?? createTexture(gl); - 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); - - if (isLast) { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } else { - 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, isLast); - - preTexture = resultTexture; - } - } - - public async setLayers(layers: ImageEffectorLayer[]) { - this.layers = layers; - - const unused = new Set(this.paramTextures.keys()); + public async render(layers: ImageEffectorLayer[]) { + const compositorLayers: Parameters['render']>[0] = []; for (const layer of layers) { - const fx = this.fxs.find(fx => fx.id === layer.fxId); - if (fx == null) continue; - - for (const k of Object.keys(layer.params)) { - const paramDef = fx.params[k]; - if (paramDef == null) continue; - if (paramDef.type !== 'texture') continue; - const v = getValue(layer.params, k); - if (v == null) continue; - - const textureKey = this.getTextureKeyForParam(v); - unused.delete(textureKey); - if (this.paramTextures.has(textureKey)) continue; - - if (_DEV_) console.log(`Baking texture of <${textureKey}>...`); - - const texture = - v.type === 'text' ? await createTextureFromText(this.gl, v.text) : - v.type === 'url' ? await createTextureFromUrl(this.gl, v.url) : - v.type === 'qr' ? await createTextureFromQr(this.gl, { data: v.data }) : - null; - if (texture == null) continue; - - this.paramTextures.set(textureKey, texture); - } + compositorLayers.push({ + id: layer.id, + functionId: layer.fxId, + params: layer.params, + }); } - for (const k of unused) { - if (_DEV_) console.log(`Dispose unused texture <${k}>...`); - this.gl.deleteTexture(this.paramTextures.get(k)!.texture); - this.paramTextures.delete(k); - } - - this.render(); + this.compositor.render(compositorLayers as Parameters[0]); } public changeResolution(width: number, height: number) { - this.renderWidth = width; - this.renderHeight = height; - if (this.canvas) { - this.canvas.width = this.renderWidth; - this.canvas.height = this.renderHeight; - } - this.gl.viewport(0, 0, this.renderWidth, this.renderHeight); - } - - private getTextureKeyForParam(v: ParamTypeToPrimitive['texture']) { - if (v == null) return ''; - return ( - v.type === 'text' ? `text:${v.text}` : - v.type === 'url' ? `url:${v.url}` : - v.type === 'qr' ? `qr:${v.data}` : - '' - ); + this.compositor.changeResolution(width, height); } /* * disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意 */ public destroy(disposeCanvas = true) { - this.gl.deleteProgram(this.nopProgram); - - for (const shader of this.shaderCache.values()) { - this.gl.deleteProgram(shader); - } - this.shaderCache.clear(); - - for (const texture of this.perLayerResultTextures.values()) { - this.gl.deleteTexture(texture); - } - this.perLayerResultTextures.clear(); - - for (const framebuffer of this.perLayerResultFrameBuffers.values()) { - this.gl.deleteFramebuffer(framebuffer); - } - this.perLayerResultFrameBuffers.clear(); - - for (const texture of this.paramTextures.values()) { - this.gl.deleteTexture(texture.texture); - } - this.paramTextures.clear(); - - this.gl.deleteTexture(this.originalImageTexture); - - if (disposeCanvas) { - const loseContextExt = this.gl.getExtension('WEBGL_lose_context'); - if (loseContextExt) loseContextExt.loseContext(); - } + this.compositor.destroy(disposeCanvas); } } - -function createTexture(gl: WebGL2RenderingContext): WebGLTexture { - const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.bindTexture(gl.TEXTURE_2D, null); - return texture; -} - -async function createTextureFromUrl(gl: WebGL2RenderingContext, imageUrl: string | null): Promise<{ texture: WebGLTexture, width: number, height: number } | null> { - if (imageUrl == null || imageUrl.trim() === '') return null; - - const image = await new Promise((resolve, reject) => { - const img = new Image(); - img.onload = () => resolve(img); - img.onerror = reject; - img.src = getProxiedImageUrl(imageUrl); // CORS対策 - }).catch(() => null); - - if (image == null) return null; - - const texture = createTexture(gl); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - 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); - - return { - texture, - width: image.width, - height: image.height, - }; -} - -async function createTextureFromText(gl: WebGL2RenderingContext, text: string | null, resolution = 2048): Promise<{ texture: WebGLTexture, width: number, height: number } | null> { - if (text == null || text.trim() === '') return null; - - const ctx = window.document.createElement('canvas').getContext('2d')!; - ctx.canvas.width = resolution; - ctx.canvas.height = resolution / 4; - const fontSize = resolution / 32; - const margin = fontSize / 2; - ctx.shadowColor = '#000000'; - ctx.shadowBlur = fontSize / 4; - - //ctx.fillStyle = '#00ff00'; - //ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - - ctx.fillStyle = '#ffffff'; - ctx.font = `bold ${fontSize}px sans-serif`; - ctx.textBaseline = 'middle'; - - ctx.fillText(text, margin, ctx.canvas.height / 2); - - const textMetrics = ctx.measureText(text); - const cropWidth = (Math.ceil(textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft) + margin + margin); - const cropHeight = (Math.ceil(textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent) + margin + margin); - const data = ctx.getImageData(0, (ctx.canvas.height / 2) - (cropHeight / 2), ctx.canvas.width, ctx.canvas.height); - - const texture = createTexture(gl); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, cropWidth, cropHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, data); - gl.bindTexture(gl.TEXTURE_2D, null); - - const info = { - texture: texture, - width: cropWidth, - height: cropHeight, - }; - - ctx.canvas.remove(); - - return info; -} - -async function createTextureFromQr(gl: WebGL2RenderingContext, options: { data: string | null }, resolution = 512): Promise<{ texture: WebGLTexture, width: number, height: number } | null> { - const $i = ensureSignin(); - - const qrCodeInstance = new QRCodeStyling({ - width: resolution, - height: resolution, - margin: 42, - type: 'canvas', - data: options.data == null || options.data === '' ? `${url}/users/${$i.id}` : options.data, - image: $i.avatarUrl, - qrOptions: { - typeNumber: 0, - mode: 'Byte', - errorCorrectionLevel: 'H', - }, - imageOptions: { - hideBackgroundDots: true, - imageSize: 0.3, - margin: 16, - crossOrigin: 'anonymous', - }, - dotsOptions: { - type: 'dots', - }, - cornersDotOptions: { - type: 'dot', - }, - cornersSquareOptions: { - type: 'extra-rounded', - }, - }); - - const blob = await qrCodeInstance.getRawData('png') as Blob | null; - if (blob == null) return null; - - const image = await window.createImageBitmap(blob); - - const texture = createTexture(gl); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, resolution, resolution, 0, gl.RGBA, gl.UNSIGNED_BYTE, image); - gl.bindTexture(gl.TEXTURE_2D, null); - - return { - texture, - width: resolution, - height: resolution, - }; -} diff --git a/packages/frontend/src/utility/image-effector/fxs.ts b/packages/frontend/src/utility/image-effector/fxs.ts index 2b20cc1f99..1fd0ad6ed7 100644 --- a/packages/frontend/src/utility/image-effector/fxs.ts +++ b/packages/frontend/src/utility/image-effector/fxs.ts @@ -3,43 +3,47 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { FX_checker } from './fxs/checker.js'; -import { FX_chromaticAberration } from './fxs/chromaticAberration.js'; -import { FX_colorAdjust } from './fxs/colorAdjust.js'; -import { FX_colorClamp } from './fxs/colorClamp.js'; -import { FX_colorClampAdvanced } from './fxs/colorClampAdvanced.js'; -import { FX_distort } from './fxs/distort.js'; -import { FX_polkadot } from './fxs/polkadot.js'; -import { FX_tearing } from './fxs/tearing.js'; -import { FX_grayscale } from './fxs/grayscale.js'; -import { FX_invert } from './fxs/invert.js'; -import { FX_mirror } from './fxs/mirror.js'; -import { FX_stripe } from './fxs/stripe.js'; -import { FX_threshold } from './fxs/threshold.js'; -import { FX_zoomLines } from './fxs/zoomLines.js'; -import { FX_blockNoise } from './fxs/blockNoise.js'; -import { FX_fill } from './fxs/fill.js'; -import { FX_blur } from './fxs/blur.js'; -import { FX_pixelate } from './fxs/pixelate.js'; -import type { ImageEffectorFx } from './ImageEffector.js'; +import * as checker from '../image-compositor-functions/checker.js'; +import * as chromaticAberration from '../image-compositor-functions/chromaticAberration.js'; +import * as colorAdjust from '../image-compositor-functions/colorAdjust.js'; +import * as colorClamp from '../image-compositor-functions/colorClamp.js'; +import * as colorClampAdvanced from '../image-compositor-functions/colorClampAdvanced.js'; +import * as distort from '../image-compositor-functions/distort.js'; +import * as polkadot from '../image-compositor-functions/polkadot.js'; +import * as tearing from '../image-compositor-functions/tearing.js'; +import * as grayscale from '../image-compositor-functions/grayscale.js'; +import * as invert from '../image-compositor-functions/invert.js'; +import * as mirror from '../image-compositor-functions/mirror.js'; +import * as stripe from '../image-compositor-functions/stripe.js'; +import * as threshold from '../image-compositor-functions/threshold.js'; +import * as zoomLines from '../image-compositor-functions/zoomLines.js'; +import * as blockNoise from '../image-compositor-functions/blockNoise.js'; +import * as fill from '../image-compositor-functions/fill.js'; +import * as blur from '../image-compositor-functions/blur.js'; +import * as pixelate from '../image-compositor-functions/pixelate.js'; +import type { ImageCompositorFunction } from '@/lib/ImageCompositor.js'; +import type { ImageEffectorUiDefinition } from './ImageEffector.js'; -export const FXS = [ - FX_mirror, - FX_invert, - FX_grayscale, - FX_colorAdjust, - FX_colorClamp, - FX_colorClampAdvanced, - FX_distort, - FX_threshold, - FX_zoomLines, - FX_stripe, - FX_polkadot, - FX_checker, - FX_chromaticAberration, - FX_tearing, - FX_blockNoise, - FX_fill, - FX_blur, - FX_pixelate, -] as const satisfies ImageEffectorFx[]; +export const FXS = { + checker, + chromaticAberration, + colorAdjust, + colorClamp, + colorClampAdvanced, + distort, + polkadot, + tearing, + grayscale, + invert, + mirror, + stripe, + threshold, + zoomLines, + blockNoise, + fill, + blur, + pixelate, +} as const satisfies Record; + readonly uiDefinition: ImageEffectorUiDefinition; +}>; diff --git a/packages/frontend/src/utility/image-effector/fxs/grayscale.ts b/packages/frontend/src/utility/image-effector/fxs/grayscale.ts deleted file mode 100644 index 055e8b4618..0000000000 --- a/packages/frontend/src/utility/image-effector/fxs/grayscale.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineImageEffectorFx } from '../ImageEffector.js'; -import shader from './grayscale.glsl'; -import { i18n } from '@/i18n.js'; - -export const FX_grayscale = defineImageEffectorFx({ - id: 'grayscale', - name: i18n.ts._imageEffector._fxs.grayscale, - shader, - uniforms: [] as const, - params: { - }, - main: ({ gl, params }) => { - }, -}); diff --git a/packages/frontend/src/utility/image-frame-renderer/ImageFrameRenderer.ts b/packages/frontend/src/utility/image-frame-renderer/ImageFrameRenderer.ts new file mode 100644 index 0000000000..9e97728785 --- /dev/null +++ b/packages/frontend/src/utility/image-frame-renderer/ImageFrameRenderer.ts @@ -0,0 +1,270 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import QRCodeStyling from 'qr-code-styling'; +import { url } from '@@/js/config.js'; +import ExifReader from 'exifreader'; +import { FN_frame } from './frame.js'; +import { ImageCompositor } from '@/lib/ImageCompositor.js'; +import { ensureSignin } from '@/i.js'; + +const $i = ensureSignin(); + +type LabelParams = { + enabled: boolean; + scale: number; + padding: number; + textBig: string; + textSmall: string; + centered: boolean; + withQrCode: boolean; +}; + +export type ImageFrameParams = { + borderThickness: number; + labelTop: LabelParams; + labelBottom: LabelParams; + bgColor: [r: number, g: number, b: number]; + fgColor: [r: number, g: number, b: number]; + font: 'serif' | 'sans-serif'; + borderRadius: number; // TODO +}; + +export type ImageFramePreset = { + id: string; + name: string; + params: ImageFrameParams; +}; + +export class ImageFrameRenderer { + private compositor: ImageCompositor<{ frame: typeof FN_frame }>; + private image: HTMLImageElement | ImageBitmap; + private exif: ExifReader.Tags | null; + private caption: string | null = null; + private filename: string | null = null; + private renderAsPreview = false; + + constructor(options: { + canvas: HTMLCanvasElement, + image: HTMLImageElement | ImageBitmap, + exif: ExifReader.Tags | null, + filename: string | null, + caption: string | null, + renderAsPreview?: boolean, + }) { + this.image = options.image; + this.exif = options.exif; + this.caption = options.caption ?? null; + this.filename = options.filename ?? null; + this.renderAsPreview = options.renderAsPreview ?? false; + + this.compositor = new ImageCompositor({ + canvas: options.canvas, + renderWidth: 1, + renderHeight: 1, + image: null, + functions: { frame: FN_frame }, + }); + + this.compositor.registerTexture('image', this.image); + } + + private interpolateTemplateText(text: string) { + const DateTimeOriginal = this.exif == null ? '2012:03:04 5:06:07' : this.exif.DateTimeOriginal?.description; + const Model = this.exif == null ? 'Example camera' : this.exif.Model?.description; + const LensModel = this.exif == null ? 'Example lens 123mm f/1.23' : this.exif.LensModel?.description; + const FocalLength = this.exif == null ? '123mm' : this.exif.FocalLength?.description; + const FocalLengthIn35mmFilm = this.exif == null ? '123mm' : this.exif.FocalLengthIn35mmFilm?.description; + const ExposureTime = this.exif == null ? '1/234' : this.exif.ExposureTime?.description; + const FNumber = this.exif == null ? '1.23' : this.exif.FNumber?.description; + const ISOSpeedRatings = this.exif == null ? '123' : this.exif.ISOSpeedRatings?.description; + const GPSLatitude = this.exif == null ? '123.000000000000123' : this.exif.GPSLatitude?.description; + const GPSLongitude = this.exif == null ? '456.000000000000123' : this.exif.GPSLongitude?.description; + return text.replaceAll(/\{(\w+)\}/g, (_: string, key: string) => { + const meta_date = DateTimeOriginal ?? '????:??:?? ??:??:??'; + const date = meta_date.split(' ')[0].replaceAll(':', '/'); + switch (key) { + case 'caption': return this.caption ?? '?'; + case 'filename': return this.filename ?? '?'; + case 'filename_without_ext': return this.filename?.replace(/\.[^/.]+$/, '') ?? '?'; + case 'year': return date.split('/')[0]; + case 'month': return date.split('/')[1].replace(/^0/, ''); + case 'day': return date.split('/')[2].replace(/^0/, ''); + case 'hour': return meta_date.split(' ')[1].split(':')[0].replace(/^0/, ''); + case 'minute': return meta_date.split(' ')[1].split(':')[1].replace(/^0/, ''); + case 'second': return meta_date.split(' ')[1].split(':')[2].replace(/^0/, ''); + case '0month': return date.split('/')[1]; + case '0day': return date.split('/')[2]; + case '0hour': return meta_date.split(' ')[1].split(':')[0]; + case '0minute': return meta_date.split(' ')[1].split(':')[1]; + case '0second': return meta_date.split(' ')[1].split(':')[2]; + case 'camera_model': return Model ?? '?'; + case 'camera_lens_model': return LensModel ?? '?'; + case 'camera_mm': return FocalLength?.replace(' mm', '').replace('mm', '') ?? '?'; + case 'camera_mm_35': return FocalLengthIn35mmFilm?.replace(' mm', '').replace('mm', '') ?? '?'; + case 'camera_f': return FNumber?.replace('f/', '') ?? '?'; + case 'camera_s': return ExposureTime ?? '?'; + case 'camera_iso': return ISOSpeedRatings ?? '?'; + case 'gps_lat': return GPSLatitude ?? '?'; + case 'gps_long': return GPSLongitude ?? '?'; + default: return '?'; + } + }); + } + + private async renderLabel(renderWidth: number, renderHeight: number, paddingLeft: number, paddingRight: number, imageAreaH: number, fgColor: [number, number, number], font: string, params: LabelParams) { + const scaleBase = imageAreaH * params.scale; + const labelCanvasCtx = window.document.createElement('canvas').getContext('2d')!; + labelCanvasCtx.canvas.width = renderWidth; + labelCanvasCtx.canvas.height = renderHeight; + const fontSize = scaleBase / 30; + const textsMarginLeft = Math.max(fontSize * 2, paddingLeft); + const textsMarginRight = textsMarginLeft; + const withQrCode = params.withQrCode; + const qrSize = scaleBase * 0.1; + const qrMarginRight = Math.max((labelCanvasCtx.canvas.height - qrSize) / 2, paddingRight); + + labelCanvasCtx.fillStyle = `rgb(${Math.floor(fgColor[0] * 255)}, ${Math.floor(fgColor[1] * 255)}, ${Math.floor(fgColor[2] * 255)})`; + labelCanvasCtx.font = `bold ${fontSize}px ${font}`; + labelCanvasCtx.textBaseline = 'middle'; + + const titleY = params.textSmall === '' ? (labelCanvasCtx.canvas.height / 2) : (labelCanvasCtx.canvas.height / 2) - (fontSize * 0.9); + if (params.centered) { + labelCanvasCtx.textAlign = 'center'; + labelCanvasCtx.fillText(this.interpolateTemplateText(params.textBig), labelCanvasCtx.canvas.width / 2, titleY, labelCanvasCtx.canvas.width - textsMarginLeft - textsMarginRight); + } else { + labelCanvasCtx.textAlign = 'left'; + labelCanvasCtx.fillText(this.interpolateTemplateText(params.textBig), textsMarginLeft, titleY, labelCanvasCtx.canvas.width - textsMarginLeft - (withQrCode ? (qrSize + qrMarginRight + (fontSize * 1)) : textsMarginRight)); + } + + labelCanvasCtx.fillStyle = `rgba(${Math.floor(fgColor[0] * 255)}, ${Math.floor(fgColor[1] * 255)}, ${Math.floor(fgColor[2] * 255)}, 0.5)`; + labelCanvasCtx.font = `${fontSize * 0.85}px ${font}`; + labelCanvasCtx.textBaseline = 'middle'; + + const textY = params.textBig === '' ? (labelCanvasCtx.canvas.height / 2) : (labelCanvasCtx.canvas.height / 2) + (fontSize * 0.9); + if (params.centered) { + labelCanvasCtx.textAlign = 'center'; + labelCanvasCtx.fillText(this.interpolateTemplateText(params.textSmall), labelCanvasCtx.canvas.width / 2, textY, labelCanvasCtx.canvas.width - textsMarginLeft - textsMarginRight); + } else { + labelCanvasCtx.textAlign = 'left'; + labelCanvasCtx.fillText(this.interpolateTemplateText(params.textSmall), textsMarginLeft, textY, labelCanvasCtx.canvas.width - textsMarginLeft - (withQrCode ? (qrSize + qrMarginRight + (fontSize * 1)) : textsMarginRight)); + } + + if (withQrCode) { + try { + const qrCodeInstance = new QRCodeStyling({ + width: labelCanvasCtx.canvas.height, + height: labelCanvasCtx.canvas.height, + margin: 0, + type: 'canvas', + data: `${url}/users/${$i.id}`, + //image: $i.avatarUrl, + qrOptions: { + typeNumber: 0, + mode: 'Byte', + errorCorrectionLevel: 'H', + }, + imageOptions: { + hideBackgroundDots: true, + imageSize: 0.3, + margin: 16, + crossOrigin: 'anonymous', + }, + dotsOptions: { + type: 'dots', + roundSize: false, + color: `rgb(${Math.floor(fgColor[0] * 255)}, ${Math.floor(fgColor[1] * 255)}, ${Math.floor(fgColor[2] * 255)})`, + }, + backgroundOptions: { + color: 'transparent', + }, + cornersDotOptions: { + type: 'dot', + }, + cornersSquareOptions: { + type: 'extra-rounded', + }, + }); + + const blob = await qrCodeInstance.getRawData('png') as Blob | null; + if (blob == null) throw new Error('Failed to generate QR code'); + + const qrImageBitmap = await window.createImageBitmap(blob); + + labelCanvasCtx.drawImage( + qrImageBitmap, + labelCanvasCtx.canvas.width - qrSize - qrMarginRight, + (labelCanvasCtx.canvas.height - qrSize) / 2, + qrSize, + qrSize, + ); + qrImageBitmap.close(); + } catch (err) { + // nop + } + } + + return labelCanvasCtx.getImageData(0, 0, labelCanvasCtx.canvas.width, labelCanvasCtx.canvas.height); ; + } + + public async render(params: ImageFrameParams): Promise { + let imageAreaW = this.image.width; + let imageAreaH = this.image.height; + + if (this.renderAsPreview) { + const MAX_W = 1000; + const MAX_H = 1000; + + if (imageAreaW > MAX_W || imageAreaH > MAX_H) { + const scale = Math.min(MAX_W / imageAreaW, MAX_H / imageAreaH); + imageAreaW = Math.floor(imageAreaW * scale); + imageAreaH = Math.floor(imageAreaH * scale); + } + } + + const paddingLeft = Math.floor(imageAreaH * params.borderThickness); + const paddingRight = Math.floor(imageAreaH * params.borderThickness); + const paddingTop = params.labelTop.enabled ? Math.floor(imageAreaH * params.labelTop.padding) : Math.floor(imageAreaH * params.borderThickness); + const paddingBottom = params.labelBottom.enabled ? Math.floor(imageAreaH * params.labelBottom.padding) : Math.floor(imageAreaH * params.borderThickness); + const renderWidth = imageAreaW + paddingLeft + paddingRight; + const renderHeight = imageAreaH + paddingTop + paddingBottom; + + if (params.labelTop.enabled) { + const topLabelImage = await this.renderLabel(renderWidth, paddingTop, paddingLeft, paddingRight, imageAreaH, params.fgColor, params.font, params.labelTop); + this.compositor.registerTexture('topLabel', topLabelImage); + } + + if (params.labelBottom.enabled) { + const bottomLabelImage = await this.renderLabel(renderWidth, paddingBottom, paddingLeft, paddingRight, imageAreaH, params.fgColor, params.font, params.labelBottom); + this.compositor.registerTexture('bottomLabel', bottomLabelImage); + } + + this.compositor.changeResolution(renderWidth, renderHeight); + + this.compositor.render([{ + functionId: 'frame', + id: 'a', + params: { + image: 'image', + topLabel: 'topLabel', + bottomLabel: 'bottomLabel', + topLabelEnabled: params.labelTop.enabled, + bottomLabelEnabled: params.labelBottom.enabled, + paddingLeft: paddingLeft / renderWidth, + paddingRight: paddingRight / renderWidth, + paddingTop: paddingTop / renderHeight, + paddingBottom: paddingBottom / renderHeight, + bg: params.bgColor, + }, + }]); + } + + /* + * disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意 + */ + public destroy(disposeCanvas = true): void { + this.compositor.destroy(disposeCanvas); + } +} diff --git a/packages/frontend/src/utility/image-frame-renderer/frame.glsl b/packages/frontend/src/utility/image-frame-renderer/frame.glsl new file mode 100644 index 0000000000..aa9dde5ad8 --- /dev/null +++ b/packages/frontend/src/utility/image-frame-renderer/frame.glsl @@ -0,0 +1,61 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform sampler2D u_image; +uniform sampler2D u_topLabel; +uniform sampler2D u_bottomLabel; +uniform bool u_topLabelEnabled; +uniform bool u_bottomLabelEnabled; +uniform float u_paddingTop; +uniform float u_paddingBottom; +uniform float u_paddingLeft; +uniform float u_paddingRight; +uniform vec3 u_bg; +out vec4 out_color; + +float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) { + return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin)); +} + +vec3 blendAlpha(vec3 bg, vec4 fg) { + return fg.a * fg.rgb + (1.0 - fg.a) * bg; +} + +void main() { + vec4 bg = vec4(u_bg, 1.0); + + vec4 image_color = texture(u_image, vec2( + remap(in_uv.x, u_paddingLeft, 1.0 - u_paddingRight, 0.0, 1.0), + remap(in_uv.y, u_paddingTop, 1.0 - u_paddingBottom, 0.0, 1.0) + )); + + vec4 topLabel_color = u_topLabelEnabled ? texture(u_topLabel, vec2( + in_uv.x, + remap(in_uv.y, 0.0, u_paddingTop, 0.0, 1.0) + )) : bg; + + vec4 bottomLabel_color = u_bottomLabelEnabled ? texture(u_bottomLabel, vec2( + in_uv.x, + remap(in_uv.y, 1.0 - u_paddingBottom, 1.0, 0.0, 1.0) + )) : bg; + + if (in_uv.y < u_paddingTop) { + out_color = vec4(blendAlpha(bg.rgb, topLabel_color), 1.0); + } else if (in_uv.y > (1.0 - u_paddingBottom)) { + out_color = vec4(blendAlpha(bg.rgb, bottomLabel_color), 1.0); + } else { + if (in_uv.y > u_paddingTop && in_uv.x > u_paddingLeft && in_uv.x < (1.0 - u_paddingRight)) { + out_color = image_color; + } else { + out_color = bg; + } + } +} diff --git a/packages/frontend/src/utility/image-frame-renderer/frame.ts b/packages/frontend/src/utility/image-frame-renderer/frame.ts new file mode 100644 index 0000000000..aeca45c1ec --- /dev/null +++ b/packages/frontend/src/utility/image-frame-renderer/frame.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import shader from './frame.glsl'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; + +export const FN_frame = defineImageCompositorFunction<{ + image: string | null; + topLabel: string | null; + bottomLabel: string | null; + topLabelEnabled: boolean; + bottomLabelEnabled: boolean; + paddingTop: number; + paddingBottom: number; + paddingLeft: number; + paddingRight: number; + bg: [number, number, number]; +}>({ + shader, + main: ({ gl, u, params, textures }) => { + if (params.image == null) return; + const image = textures.get(params.image); + if (image == null) return; + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, image.texture); + gl.uniform1i(u.image, 1); + + gl.uniform1i(u.topLabelEnabled, params.topLabelEnabled ? 1 : 0); + gl.uniform1i(u.bottomLabelEnabled, params.bottomLabelEnabled ? 1 : 0); + gl.uniform1f(u.paddingTop, params.paddingTop); + gl.uniform1f(u.paddingBottom, params.paddingBottom); + gl.uniform1f(u.paddingLeft, params.paddingLeft); + gl.uniform1f(u.paddingRight, params.paddingRight); + gl.uniform3f(u.bg, params.bg[0], params.bg[1], params.bg[2]); + + if (params.topLabelEnabled && params.topLabel != null) { + const topLabel = textures.get(params.topLabel); + if (topLabel) { + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, topLabel.texture); + gl.uniform1i(u.topLabel, 2); + } + } + + if (params.bottomLabelEnabled && params.bottomLabel != null) { + const bottomLabel = textures.get(params.bottomLabel); + if (bottomLabel) { + gl.activeTexture(gl.TEXTURE3); + gl.bindTexture(gl.TEXTURE_2D, bottomLabel.texture); + gl.uniform1i(u.bottomLabel, 3); + } + } + }, +}); diff --git a/packages/frontend/src/utility/watermark.ts b/packages/frontend/src/utility/watermark.ts deleted file mode 100644 index 1b46721a2b..0000000000 --- a/packages/frontend/src/utility/watermark.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { ImageEffectorFx, ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js'; -import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js'; -import { FX_stripe } from '@/utility/image-effector/fxs/stripe.js'; -import { FX_polkadot } from '@/utility/image-effector/fxs/polkadot.js'; -import { FX_checker } from '@/utility/image-effector/fxs/checker.js'; -import { ImageEffector } from '@/utility/image-effector/ImageEffector.js'; - -const WATERMARK_FXS = [ - FX_watermarkPlacement, - FX_stripe, - FX_polkadot, - FX_checker, -] as const satisfies ImageEffectorFx[]; - -type Align = { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; margin?: number; }; - -export type WatermarkPreset = { - id: string; - name: string; - layers: ({ - id: string; - type: 'text'; - text: string; - repeat: boolean; - noBoundingBoxExpansion: boolean; - scale: number; - angle: number; - align: Align; - opacity: number; - } | { - id: string; - type: 'image'; - imageUrl: string | null; - imageId: string | null; - cover: boolean; - repeat: boolean; - noBoundingBoxExpansion: boolean; - scale: number; - angle: number; - align: Align; - opacity: number; - } | { - id: string; - type: 'qr'; - data: string; - scale: number; - align: Align; - opacity: number; - } | { - id: string; - type: 'stripe'; - angle: number; - frequency: number; - threshold: number; - color: [r: number, g: number, b: number]; - opacity: number; - } | { - id: string; - type: 'polkadot'; - angle: number; - scale: number; - majorRadius: number; - majorOpacity: number; - minorDivisions: number; - minorRadius: number; - minorOpacity: number; - color: [r: number, g: number, b: number]; - opacity: number; - } | { - id: string; - type: 'checker'; - angle: number; - scale: number; - color: [r: number, g: number, b: number]; - opacity: number; - })[]; -}; - -export class WatermarkRenderer { - private effector: ImageEffector; - private layers: WatermarkPreset['layers'] = []; - - constructor(options: { - canvas: HTMLCanvasElement, - renderWidth: number, - renderHeight: number, - image: HTMLImageElement | ImageBitmap, - }) { - this.effector = new ImageEffector({ - canvas: options.canvas, - renderWidth: options.renderWidth, - renderHeight: options.renderHeight, - image: options.image, - fxs: WATERMARK_FXS, - }); - } - - private makeImageEffectorLayers(): ImageEffectorLayer[] { - return this.layers.map(layer => { - if (layer.type === 'text') { - return { - fxId: 'watermarkPlacement', - id: layer.id, - params: { - repeat: layer.repeat, - noBoundingBoxExpansion: layer.noBoundingBoxExpansion, - scale: layer.scale, - align: layer.align, - angle: layer.angle, - opacity: layer.opacity, - cover: false, - watermark: { - type: 'text', - text: layer.text, - }, - }, - }; - } else if (layer.type === 'image') { - return { - fxId: 'watermarkPlacement', - id: layer.id, - params: { - repeat: layer.repeat, - noBoundingBoxExpansion: layer.noBoundingBoxExpansion, - scale: layer.scale, - align: layer.align, - angle: layer.angle, - opacity: layer.opacity, - cover: layer.cover, - watermark: { - type: 'url', - url: layer.imageUrl, - }, - }, - }; - } else if (layer.type === 'qr') { - return { - fxId: 'watermarkPlacement', - id: layer.id, - params: { - repeat: false, - scale: layer.scale, - align: layer.align, - angle: 0, - opacity: layer.opacity, - cover: false, - watermark: { - type: 'qr', - data: layer.data, - }, - }, - }; - } else if (layer.type === 'stripe') { - return { - fxId: 'stripe', - id: layer.id, - params: { - angle: layer.angle, - frequency: layer.frequency, - threshold: layer.threshold, - color: layer.color, - opacity: layer.opacity, - }, - }; - } else if (layer.type === 'polkadot') { - return { - fxId: 'polkadot', - id: layer.id, - params: { - angle: layer.angle, - scale: layer.scale, - majorRadius: layer.majorRadius, - majorOpacity: layer.majorOpacity, - minorDivisions: layer.minorDivisions, - minorRadius: layer.minorRadius, - minorOpacity: layer.minorOpacity, - color: layer.color, - }, - }; - } else if (layer.type === 'checker') { - return { - fxId: 'checker', - id: layer.id, - params: { - angle: layer.angle, - scale: layer.scale, - color: layer.color, - opacity: layer.opacity, - }, - }; - } else { - throw new Error(`Unrecognized layer type: ${(layer as any).type}`); - } - }); - } - - public async setLayers(layers: WatermarkPreset['layers']) { - this.layers = layers; - await this.effector.setLayers(this.makeImageEffectorLayers()); - this.render(); - } - - public render(): void { - this.effector.render(); - } - - /* - * disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意 - */ - public destroy(disposeCanvas = true): void { - this.effector.destroy(disposeCanvas); - } -} diff --git a/packages/frontend/src/utility/watermark/WatermarkRenderer.ts b/packages/frontend/src/utility/watermark/WatermarkRenderer.ts new file mode 100644 index 0000000000..766d45148a --- /dev/null +++ b/packages/frontend/src/utility/watermark/WatermarkRenderer.ts @@ -0,0 +1,332 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import QRCodeStyling from 'qr-code-styling'; +import { url, host } from '@@/js/config.js'; +import { getProxiedImageUrl } from '../media-proxy.js'; +import { fn as fn_watermark } from './watermark.js'; +import { fn as fn_stripe } from '@/utility/image-compositor-functions/stripe.js'; +import { fn as fn_poladot } from '@/utility/image-compositor-functions/polkadot.js'; +import { fn as fn_checker } from '@/utility/image-compositor-functions/checker.js'; +import { ImageCompositor } from '@/lib/ImageCompositor.js'; +import { ensureSignin } from '@/i.js'; + +type Align = { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; margin?: number; }; + +export type WatermarkLayers = ({ + id: string; + type: 'text'; + text: string; + repeat: boolean; + noBoundingBoxExpansion: boolean; + scale: number; + angle: number; + align: Align; + opacity: number; +} | { + id: string; + type: 'image'; + imageUrl: string | null; + imageId: string | null; + cover: boolean; + repeat: boolean; + noBoundingBoxExpansion: boolean; + scale: number; + angle: number; + align: Align; + opacity: number; +} | { + id: string; + type: 'qr'; + data: string; + scale: number; + align: Align; + opacity: number; +} | { + id: string; + type: 'stripe'; + angle: number; + frequency: number; + threshold: number; + color: [r: number, g: number, b: number]; + opacity: number; +} | { + id: string; + type: 'polkadot'; + angle: number; + scale: number; + majorRadius: number; + majorOpacity: number; + minorDivisions: number; + minorRadius: number; + minorOpacity: number; + color: [r: number, g: number, b: number]; + opacity: number; +} | { + id: string; + type: 'checker'; + angle: number; + scale: number; + color: [r: number, g: number, b: number]; + opacity: number; +})[]; + +export type WatermarkPreset = { + id: string; + name: string; + layers: WatermarkLayers; +}; + +type WatermarkRendererImageCompositor = ImageCompositor<{ + watermark: typeof fn_watermark; + stripe: typeof fn_stripe; + polkadot: typeof fn_poladot; + checker: typeof fn_checker; +}>; + +export class WatermarkRenderer { + private compositor: WatermarkRendererImageCompositor; + + constructor(options: { + canvas: HTMLCanvasElement, + renderWidth: number, + renderHeight: number, + image: HTMLImageElement | ImageBitmap, + }) { + this.compositor = new ImageCompositor({ + canvas: options.canvas, + renderWidth: options.renderWidth, + renderHeight: options.renderHeight, + image: options.image, + functions: { + watermark: fn_watermark, + stripe: fn_stripe, + polkadot: fn_poladot, + checker: fn_checker, + }, + }); + } + + public async render(layers: WatermarkLayers) { + const compositorLayers: Parameters[0] = []; + + const unused = new Set(this.compositor.getKeysOfRegisteredTextures()); + + for (const layer of layers) { + if (layer.type === 'text') { + const textureKey = `text:${layer.text}`; + unused.delete(textureKey); + if (!this.compositor.hasTexture(textureKey)) { + if (_DEV_) console.log(`Baking text texture of <${textureKey}>...`); + const image = await createTextureFromText(layer.text); + if (image != null) this.compositor.registerTexture(textureKey, image); + } + + compositorLayers.push({ + functionId: 'watermark', + id: layer.id, + params: { + repeat: layer.repeat, + noBoundingBoxExpansion: layer.noBoundingBoxExpansion, + scale: layer.scale, + align: layer.align, + angle: layer.angle, + opacity: layer.opacity, + cover: false, + watermark: textureKey, + }, + }); + } else if (layer.type === 'image') { + const textureKey = `url:${layer.imageUrl}`; + unused.delete(textureKey); + if (!this.compositor.hasTexture(textureKey)) { + if (_DEV_) console.log(`Baking url image texture of <${textureKey}>...`); + const image = await createTextureFromUrl(layer.imageUrl); + if (image != null) this.compositor.registerTexture(textureKey, image); + } + + compositorLayers.push({ + functionId: 'watermark', + id: layer.id, + params: { + repeat: layer.repeat, + noBoundingBoxExpansion: layer.noBoundingBoxExpansion, + scale: layer.scale, + align: layer.align, + angle: layer.angle, + opacity: layer.opacity, + cover: layer.cover, + watermark: textureKey, + }, + }); + } else if (layer.type === 'qr') { + const textureKey = `qr:${layer.data}`; + unused.delete(textureKey); + if (!this.compositor.hasTexture(textureKey)) { + if (_DEV_) console.log(`Baking qr texture of <${textureKey}>...`); + const image = await createTextureFromQr({ data: layer.data }); + if (image != null) this.compositor.registerTexture(textureKey, image); + } + + compositorLayers.push({ + functionId: 'watermark', + id: layer.id, + params: { + repeat: false, + scale: layer.scale, + align: layer.align, + angle: 0, + opacity: layer.opacity, + cover: false, + watermark: textureKey, + }, + }); + } else if (layer.type === 'stripe') { + compositorLayers.push({ + functionId: 'stripe', + id: layer.id, + params: { + angle: layer.angle, + frequency: layer.frequency, + threshold: layer.threshold, + color: layer.color, + opacity: layer.opacity, + }, + }); + } else if (layer.type === 'polkadot') { + compositorLayers.push({ + functionId: 'polkadot', + id: layer.id, + params: { + angle: layer.angle, + scale: layer.scale, + majorRadius: layer.majorRadius, + majorOpacity: layer.majorOpacity, + minorDivisions: layer.minorDivisions, + minorRadius: layer.minorRadius, + minorOpacity: layer.minorOpacity, + color: layer.color, + }, + }); + } else if (layer.type === 'checker') { + compositorLayers.push({ + functionId: 'checker', + id: layer.id, + params: { + angle: layer.angle, + scale: layer.scale, + color: layer.color, + opacity: layer.opacity, + }, + }); + } else { + throw new Error(`Unrecognized layer type: ${(layer as any).type}`); + } + } + + for (const k of unused) { + if (_DEV_) console.log(`Dispose unused texture <${k}>...`); + this.compositor.unregisterTexture(k); + } + + this.compositor.render(compositorLayers); + } + + public changeResolution(width: number, height: number) { + this.compositor.changeResolution(width, height); + } + + /* + * disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意 + */ + public destroy(disposeCanvas = true): void { + this.compositor.destroy(disposeCanvas); + } +} + +async function createTextureFromUrl(imageUrl: string | null) { + if (imageUrl == null || imageUrl.trim() === '') return null; + + const image = await new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = reject; + img.src = getProxiedImageUrl(imageUrl); // CORS対策 + }).catch(() => null); + + if (image == null) return null; + + return image; +} + +async function createTextureFromText(text: string | null, resolution = 2048) { + if (text == null || text.trim() === '') return null; + + const ctx = window.document.createElement('canvas').getContext('2d')!; + ctx.canvas.width = resolution; + ctx.canvas.height = resolution / 4; + const fontSize = resolution / 32; + const margin = fontSize / 2; + ctx.shadowColor = '#000000'; + ctx.shadowBlur = fontSize / 4; + + //ctx.fillStyle = '#00ff00'; + //ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${fontSize}px sans-serif`; + ctx.textBaseline = 'middle'; + + ctx.fillText(text, margin, ctx.canvas.height / 2); + + const textMetrics = ctx.measureText(text); + const cropWidth = (Math.ceil(textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft) + margin + margin); + const cropHeight = (Math.ceil(textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent) + margin + margin); + const data = ctx.getImageData(0, (ctx.canvas.height / 2) - (cropHeight / 2), cropWidth, cropHeight); + + ctx.canvas.remove(); + + return data; +} + +async function createTextureFromQr(options: { data: string | null }, resolution = 512) { + const $i = ensureSignin(); + + const qrCodeInstance = new QRCodeStyling({ + width: resolution, + height: resolution, + margin: 42, + type: 'canvas', + data: options.data == null || options.data === '' ? `${url}/users/${$i.id}` : options.data, + image: $i.avatarUrl, + qrOptions: { + typeNumber: 0, + mode: 'Byte', + errorCorrectionLevel: 'H', + }, + imageOptions: { + hideBackgroundDots: true, + imageSize: 0.3, + margin: 16, + crossOrigin: 'anonymous', + }, + dotsOptions: { + type: 'dots', + }, + cornersDotOptions: { + type: 'dot', + }, + cornersSquareOptions: { + type: 'extra-rounded', + }, + }); + + const blob = await qrCodeInstance.getRawData('png') as Blob | null; + if (blob == null) return null; + + const image = await window.createImageBitmap(blob); + + return image; +} diff --git a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.glsl b/packages/frontend/src/utility/watermark/watermark.glsl similarity index 100% rename from packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.glsl rename to packages/frontend/src/utility/watermark/watermark.glsl diff --git a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts b/packages/frontend/src/utility/watermark/watermark.ts similarity index 59% rename from packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts rename to packages/frontend/src/utility/watermark/watermark.ts index bb51ed796b..62efcd12b6 100644 --- a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts +++ b/packages/frontend/src/utility/watermark/watermark.ts @@ -3,57 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineImageEffectorFx } from '../ImageEffector.js'; -import shader from './watermarkPlacement.glsl'; +import shader from './watermark.glsl'; +import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js'; -export const FX_watermarkPlacement = defineImageEffectorFx({ - id: 'watermarkPlacement', - name: '(internal)', +export const fn = defineImageCompositorFunction>({ shader, - uniforms: ['opacity', 'scale', 'angle', 'cover', 'repeat', 'alignX', 'alignY', 'margin', 'repeatMargin', 'noBBoxExpansion', 'wmResolution', 'wmEnabled', 'watermark'] as const, - params: { - cover: { - type: 'boolean', - default: false, - }, - repeat: { - type: 'boolean', - default: false, - }, - scale: { - type: 'number', - default: 0.3, - min: 0.0, - max: 1.0, - step: 0.01, - }, - angle: { - type: 'number', - default: 0, - min: -1.0, - max: 1.0, - step: 0.01, - }, - align: { - type: 'align', - default: { x: 'right', y: 'bottom', margin: 0 }, - }, - opacity: { - type: 'number', - default: 0.75, - min: 0.0, - max: 1.0, - step: 0.01, - }, - noBoundingBoxExpansion: { - type: 'boolean', - default: false, - }, - watermark: { - type: 'texture', - default: null, - }, - }, main: ({ gl, u, params, textures }) => { // 基本パラメータ gl.uniform1f(u.opacity, params.opacity ?? 1.0); @@ -70,7 +33,7 @@ export const FX_watermarkPlacement = defineImageEffectorFx({ gl.uniform1i(u.noBBoxExpansion, params.noBoundingBoxExpansion ? 1 : 0); // ウォーターマークテクスチャ - const wm = textures.watermark; + const wm = textures.get(params.watermark); if (wm) { gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, wm.texture); diff --git a/packages/frontend/src/utility/webgl.ts b/packages/frontend/src/utility/webgl.ts index ae595b605c..334663b1a1 100644 --- a/packages/frontend/src/utility/webgl.ts +++ b/packages/frontend/src/utility/webgl.ts @@ -38,3 +38,14 @@ export function initShaderProgram(gl: WebGL2RenderingContext, vsSource: string, return shaderProgram; } + +export function createTexture(gl: WebGL2RenderingContext): WebGLTexture { + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.bindTexture(gl.TEXTURE_2D, null); + return texture; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5ffd355e4..6e45b17726 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -797,6 +797,9 @@ importers: execa: specifier: 9.6.0 version: 9.6.0 + exifreader: + specifier: 4.32.0 + version: 4.32.0 frontend-shared: specifier: workspace:* version: link:../frontend-shared @@ -4973,6 +4976,10 @@ packages: resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==} engines: {node: ^14.14.0 || >=16.0.0} + '@xmldom/xmldom@0.9.8': + resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} + engines: {node: '>=14.6'} + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -6505,6 +6512,9 @@ packages: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} + exifreader@4.32.0: + resolution: {integrity: sha512-sj1PzjpaPwSE/2MeUqoAYcfc2u7AZOGSby0FzmAkB4jjeCXgDryxzVgMwV+tJKGIkGdWkkWiUWoLSJoPHJ6V5Q==} + exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -7212,6 +7222,7 @@ packages: intersection-observer@0.12.2: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. ioredis@5.8.1: resolution: {integrity: sha512-Qho8TgIamqEPdgiMadJwzRMW3TudIg6vpg4YONokGDudy4eqRIJtDbVX72pfLBcWxvbn3qm/40TyGUObdW4tLQ==} @@ -15621,6 +15632,9 @@ snapshots: dependencies: arch: 3.0.0 + '@xmldom/xmldom@0.9.8': + optional: true + abbrev@1.1.1: {} abbrev@3.0.1: {} @@ -17465,6 +17479,10 @@ snapshots: dependencies: pify: 2.3.0 + exifreader@4.32.0: + optionalDependencies: + '@xmldom/xmldom': 0.9.8 + exit@0.1.2: {} expand-template@2.0.3: From e74ab35de3f62d0e733399e0d414c09c4887f67d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Nov 2025 11:33:32 +0000 Subject: [PATCH 29/79] Bump version to 2025.11.0-alpha.1 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8a5387aaea..009f478475 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.11.0-alpha.0", + "version": "2025.11.0-alpha.1", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 9fa6c36919..c23ea7fffa 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.11.0-alpha.0", + "version": "2025.11.0-alpha.1", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 729abbef621aea5b8b697644181915935b74bbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:39:21 +0900 Subject: [PATCH 30/79] =?UTF-8?q?feat:=20=E3=83=81=E3=83=A3=E3=83=B3?= =?UTF-8?q?=E3=83=8D=E3=83=AB=E3=83=9F=E3=83=A5=E3=83=BC=E3=83=88=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=20(#14105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add channel_muting table and entities * add channel_muting services * タイムライン取得処理への組み込み * misskey-jsの型とインターフェース生成 * Channelスキーマにミュート情報を追加 * フロントエンドの実装 * 条件が逆だったのを修正 * 期限切れミュートを掃除する機能を実装 * TLの抽出条件調節 * 名前の変更と変更不要の差分をロールバック * 修正漏れ * isChannelRelatedの条件に誤りがあった * [wip] テスト追加 * テストの追加と検出した不備の修正 * fix test * fix CHANGELOG.md * 通常はFTTにしておく * 実装忘れ対応 * fix merge * fix merge * add channel tl test * fix CHANGELOG.md * remove unused import * fix lint * fix test * fix favorite -> favorited * exclude -> include * fix CHANGELOG.md * fix CHANGELOG.md * maintenance * fix CHANGELOG.md * fix * fix ci * regenerate * fix * Revert "fix" This reverts commit 699d50c6ec798777d8e9667cb5d45a26b06bfc93. * fixed --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 2 + packages/backend/jest.config.unit.cjs | 1 + .../1761569941833-add-channel-muting.js | 38 + .../src/core/ChannelFollowingService.ts | 48 +- .../backend/src/core/ChannelMutingService.ts | 224 +++ packages/backend/src/core/CoreModule.ts | 6 + .../src/core/FanoutTimelineEndpointService.ts | 7 + .../backend/src/core/GlobalEventService.ts | 2 + .../backend/src/core/NoteCreateService.ts | 1 + .../backend/src/core/WebhookTestService.ts | 1 + .../src/core/entities/ChannelEntityService.ts | 156 +- packages/backend/src/di-symbols.ts | 1 + .../backend/src/misc/is-channel-related.ts | 31 + packages/backend/src/models/ChannelMuting.ts | 46 + packages/backend/src/models/Note.ts | 8 + .../backend/src/models/RepositoryModule.ts | 9 + packages/backend/src/models/_.ts | 3 + .../backend/src/models/json-schema/channel.ts | 4 + packages/backend/src/postgres.ts | 2 + .../CheckExpiredMutingsProcessorService.ts | 6 +- .../server/api/StreamingApiServerService.ts | 3 + .../backend/src/server/api/endpoint-list.ts | 3 + .../server/api/endpoints/antennas/notes.ts | 18 + .../api/endpoints/channels/mute/create.ts | 90 + .../api/endpoints/channels/mute/delete.ts | 73 + .../api/endpoints/channels/mute/list.ts | 49 + .../server/api/endpoints/channels/timeline.ts | 13 + .../api/endpoints/notes/hybrid-timeline.ts | 32 +- .../api/endpoints/notes/local-timeline.ts | 16 +- .../server/api/endpoints/notes/timeline.ts | 54 +- .../api/endpoints/notes/user-list-timeline.ts | 13 + .../src/server/api/endpoints/roles/notes.ts | 18 + .../src/server/api/endpoints/users/notes.ts | 30 +- .../src/server/api/stream/Connection.ts | 18 +- .../backend/src/server/api/stream/channel.ts | 10 +- .../src/server/api/stream/channels/channel.ts | 32 +- .../api/stream/channels/home-timeline.ts | 5 +- .../api/stream/channels/hybrid-timeline.ts | 29 +- packages/backend/test/e2e/antennas.ts | 21 + packages/backend/test/e2e/timelines.ts | 1493 ++++++++++++++++- packages/backend/test/jest.setup.unit.cjs | 10 + .../test/unit/ChannelFollowingService.ts | 235 +++ .../backend/test/unit/ChannelMutingService.ts | 336 ++++ .../backend/test/unit/NoteCreateService.ts | 1 + packages/backend/test/unit/misc/is-renote.ts | 1 + packages/frontend/src/pages/channel.vue | 82 +- packages/misskey-js/etc/misskey-js.api.md | 12 + packages/misskey-js/generator/package.json | 3 +- .../misskey-js/src/autogen/apiClientJSDoc.ts | 33 + packages/misskey-js/src/autogen/endpoint.ts | 6 + packages/misskey-js/src/autogen/entities.ts | 3 + packages/misskey-js/src/autogen/types.ts | 214 +++ pnpm-lock.yaml | 163 +- 53 files changed, 3564 insertions(+), 151 deletions(-) create mode 100644 packages/backend/migration/1761569941833-add-channel-muting.js create mode 100644 packages/backend/src/core/ChannelMutingService.ts create mode 100644 packages/backend/src/misc/is-channel-related.ts create mode 100644 packages/backend/src/models/ChannelMuting.ts create mode 100644 packages/backend/src/server/api/endpoints/channels/mute/create.ts create mode 100644 packages/backend/src/server/api/endpoints/channels/mute/delete.ts create mode 100644 packages/backend/src/server/api/endpoints/channels/mute/list.ts create mode 100644 packages/backend/test/jest.setup.unit.cjs create mode 100644 packages/backend/test/unit/ChannelFollowingService.ts create mode 100644 packages/backend/test/unit/ChannelMutingService.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 057ffd5566..d32ff1ec42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 2025.11.0 ### General +- Feat: チャンネルミュート機能の実装 #10649 + - チャンネルの概要画面の右上からミュートできます(リンクコピー、共有、設定と同列) - Enhance: Node.js 24.10.0をサポートするようになりました - Enhance: DockerのNode.jsが24.10.0に更新されました diff --git a/packages/backend/jest.config.unit.cjs b/packages/backend/jest.config.unit.cjs index aa5992936b..957d0635c1 100644 --- a/packages/backend/jest.config.unit.cjs +++ b/packages/backend/jest.config.unit.cjs @@ -7,6 +7,7 @@ const base = require('./jest.config.cjs') module.exports = { ...base, + globalSetup: "/test/jest.setup.unit.cjs", testMatch: [ "/test/unit/**/*.ts", "/src/**/*.test.ts", diff --git a/packages/backend/migration/1761569941833-add-channel-muting.js b/packages/backend/migration/1761569941833-add-channel-muting.js new file mode 100644 index 0000000000..e985df90ba --- /dev/null +++ b/packages/backend/migration/1761569941833-add-channel-muting.js @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddChannelMuting1761569941833 { + name = 'AddChannelMuting1761569941833' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "channel_muting" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "channelId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_aec842e98f332ebd8e12f85bad6" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_34415e3062ae7a94617496e81c" ON "channel_muting" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_4d534d7177fc59879d942e96d0" ON "channel_muting" ("channelId") `); + await queryRunner.query(`CREATE INDEX "IDX_6dd314e96806b7df65ddadff72" ON "channel_muting" ("expiresAt") `); + await queryRunner.query(`CREATE INDEX "IDX_b96870ed326ccc7fa243970965" ON "channel_muting" ("userId", "channelId") `); + await queryRunner.query(`ALTER TABLE "note" ADD "renoteChannelId" character varying(32)`); + await queryRunner.query(`COMMENT ON COLUMN "note"."renoteChannelId" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "channel_muting" ADD CONSTRAINT "FK_34415e3062ae7a94617496e81c5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "channel_muting" ADD CONSTRAINT "FK_4d534d7177fc59879d942e96d03" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "channel_muting" DROP CONSTRAINT "FK_4d534d7177fc59879d942e96d03"`); + await queryRunner.query(`ALTER TABLE "channel_muting" DROP CONSTRAINT "FK_34415e3062ae7a94617496e81c5"`); + await queryRunner.query(`COMMENT ON COLUMN "note"."renoteChannelId" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "renoteChannelId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_b96870ed326ccc7fa243970965"`); + await queryRunner.query(`DROP INDEX "public"."IDX_6dd314e96806b7df65ddadff72"`); + await queryRunner.query(`DROP INDEX "public"."IDX_4d534d7177fc59879d942e96d0"`); + await queryRunner.query(`DROP INDEX "public"."IDX_34415e3062ae7a94617496e81c"`); + await queryRunner.query(`DROP TABLE "channel_muting"`); + } +} diff --git a/packages/backend/src/core/ChannelFollowingService.ts b/packages/backend/src/core/ChannelFollowingService.ts index 12251595e2..d320a5ea36 100644 --- a/packages/backend/src/core/ChannelFollowingService.ts +++ b/packages/backend/src/core/ChannelFollowingService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; -import type { ChannelFollowingsRepository } from '@/models/_.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, MiUser } from '@/models/_.js'; import { MiChannel } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js'; @@ -23,6 +23,8 @@ export class ChannelFollowingService implements OnModuleInit { private redisClient: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, @Inject(DI.channelFollowingsRepository) private channelFollowingsRepository: ChannelFollowingsRepository, private idService: IdService, @@ -45,6 +47,50 @@ export class ChannelFollowingService implements OnModuleInit { onModuleInit() { } + /** + * フォローしているチャンネルの一覧を取得する. + * @param params + * @param [opts] + * @param {(boolean|undefined)} [opts.idOnly=false] チャンネルIDのみを取得するかどうか. ID以外のフィールドに値がセットされなくなり、他テーブルとのJOINも一切されなくなるので注意. + * @param {(boolean|undefined)} [opts.joinUser=undefined] チャンネルオーナーのユーザ情報をJOINするかどうか(falseまたは省略時はJOINしない). + * @param {(boolean|undefined)} [opts.joinBannerFile=undefined] バナー画像のドライブファイルをJOINするかどうか(falseまたは省略時はJOINしない). + */ + @bindThis + public async list( + params: { + requestUserId: MiUser['id'], + }, + opts?: { + idOnly?: boolean; + joinUser?: boolean; + joinBannerFile?: boolean; + }, + ): Promise { + if (opts?.idOnly) { + const q = this.channelFollowingsRepository.createQueryBuilder('channel_following') + .select('channel_following.followeeId') + .where('channel_following.followerId = :userId', { userId: params.requestUserId }); + + return q + .getRawMany<{ channel_following_followeeId: string }>() + .then(xs => xs.map(x => ({ id: x.channel_following_followeeId } as MiChannel))); + } else { + const q = this.channelsRepository.createQueryBuilder('channel') + .innerJoin('channel_following', 'channel_following', 'channel_following.followeeId = channel.id') + .where('channel_following.followerId = :userId', { userId: params.requestUserId }); + + if (opts?.joinUser) { + q.innerJoinAndSelect('channel.user', 'user'); + } + + if (opts?.joinBannerFile) { + q.leftJoinAndSelect('channel.banner', 'drive_file'); + } + + return q.getMany(); + } + } + @bindThis public async follow( requestUser: MiLocalUser, diff --git a/packages/backend/src/core/ChannelMutingService.ts b/packages/backend/src/core/ChannelMutingService.ts new file mode 100644 index 0000000000..bf5b848d44 --- /dev/null +++ b/packages/backend/src/core/ChannelMutingService.ts @@ -0,0 +1,224 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; +import { Brackets, In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { ChannelMutingRepository, ChannelsRepository, MiChannel, MiChannelMuting, MiUser } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js'; +import { bindThis } from '@/decorators.js'; +import { RedisKVCache } from '@/misc/cache.js'; + +@Injectable() +export class ChannelMutingService { + public mutingChannelsCache: RedisKVCache>; + + constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + @Inject(DI.channelMutingRepository) + private channelMutingRepository: ChannelMutingRepository, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + this.mutingChannelsCache = new RedisKVCache>(this.redisClient, 'channelMutingChannels', { + lifetime: 1000 * 60 * 30, // 30m + memoryCacheLifetime: 1000 * 60, // 1m + fetcher: (userId) => this.channelMutingRepository.find({ + where: { userId: userId }, + select: ['channelId'], + }).then(xs => new Set(xs.map(x => x.channelId))), + toRedisConverter: (value) => JSON.stringify(Array.from(value)), + fromRedisConverter: (value) => new Set(JSON.parse(value)), + }); + + this.redisForSub.on('message', this.onMessage); + } + + /** + * ミュートしているチャンネルの一覧を取得する. + * @param params + * @param [opts] + * @param {(boolean|undefined)} [opts.idOnly=false] チャンネルIDのみを取得するかどうか. ID以外のフィールドに値がセットされなくなり、他テーブルとのJOINも一切されなくなるので注意. + * @param {(boolean|undefined)} [opts.joinUser=undefined] チャンネルオーナーのユーザ情報をJOINするかどうか(falseまたは省略時はJOINしない). + * @param {(boolean|undefined)} [opts.joinBannerFile=undefined] バナー画像のドライブファイルをJOINするかどうか(falseまたは省略時はJOINしない). + */ + @bindThis + public async list( + params: { + requestUserId: MiUser['id'], + }, + opts?: { + idOnly?: boolean; + joinUser?: boolean; + joinBannerFile?: boolean; + }, + ): Promise { + if (opts?.idOnly) { + const q = this.channelMutingRepository.createQueryBuilder('channel_muting') + .select('channel_muting.channelId') + .where('channel_muting.userId = :userId', { userId: params.requestUserId }) + .andWhere(new Brackets(qb => { + qb.where('channel_muting.expiresAt IS NULL') + .orWhere('channel_muting.expiresAt > :now', { now: new Date() }); + })); + + return q + .getRawMany<{ channel_muting_channelId: string }>() + .then(xs => xs.map(x => ({ id: x.channel_muting_channelId } as MiChannel))); + } else { + const q = this.channelsRepository.createQueryBuilder('channel') + .innerJoin('channel_muting', 'channel_muting', 'channel_muting.channelId = channel.id') + .where('channel_muting.userId = :userId', { userId: params.requestUserId }) + .andWhere(new Brackets(qb => { + qb.where('channel_muting.expiresAt IS NULL') + .orWhere('channel_muting.expiresAt > :now', { now: new Date() }); + })); + + if (opts?.joinUser) { + q.innerJoinAndSelect('channel.user', 'user'); + } + + if (opts?.joinBannerFile) { + q.leftJoinAndSelect('channel.banner', 'drive_file'); + } + + return q.getMany(); + } + } + + /** + * 期限切れのチャンネルミュート情報を取得する. + * + * @param [opts] + * @param {(boolean|undefined)} [opts.joinUser=undefined] チャンネルミュートを設定したユーザ情報をJOINするかどうか(falseまたは省略時はJOINしない). + * @param {(boolean|undefined)} [opts.joinChannel=undefined] ミュート先のチャンネル情報をJOINするかどうか(falseまたは省略時はJOINしない). + */ + public async findExpiredMutings(opts?: { + joinUser?: boolean; + joinChannel?: boolean; + }): Promise { + const now = new Date(); + const q = this.channelMutingRepository.createQueryBuilder('channel_muting') + .where('channel_muting.expiresAt < :now', { now }); + + if (opts?.joinUser) { + q.innerJoinAndSelect('channel_muting.user', 'user'); + } + + if (opts?.joinChannel) { + q.leftJoinAndSelect('channel_muting.channel', 'channel'); + } + + return q.getMany(); + } + + /** + * 既にミュートされているかどうかをキャッシュから取得する. + * @param params + * @param params.requestUserId + */ + @bindThis + public async isMuted(params: { + requestUserId: MiUser['id'], + targetChannelId: MiChannel['id'], + }): Promise { + const mutedChannels = await this.mutingChannelsCache.get(params.requestUserId); + return (mutedChannels?.has(params.targetChannelId) ?? false); + } + + /** + * チャンネルをミュートする. + * @param params + * @param {(Date|null|undefined)} [params.expiresAt] ミュートの有効期限. nullまたは省略時は無期限. + */ + @bindThis + public async mute(params: { + requestUserId: MiUser['id'], + targetChannelId: MiChannel['id'], + expiresAt?: Date | null, + }): Promise { + await this.channelMutingRepository.insert({ + id: this.idService.gen(), + userId: params.requestUserId, + channelId: params.targetChannelId, + expiresAt: params.expiresAt, + }); + + this.globalEventService.publishInternalEvent('muteChannel', { + userId: params.requestUserId, + channelId: params.targetChannelId, + }); + } + + /** + * チャンネルのミュートを解除する. + * @param params + */ + @bindThis + public async unmute(params: { + requestUserId: MiUser['id'], + targetChannelId: MiChannel['id'], + }): Promise { + await this.channelMutingRepository.delete({ + userId: params.requestUserId, + channelId: params.targetChannelId, + }); + + this.globalEventService.publishInternalEvent('unmuteChannel', { + userId: params.requestUserId, + channelId: params.targetChannelId, + }); + } + + /** + * 期限切れのチャンネルミュート情報を削除する. + */ + @bindThis + public async eraseExpiredMutings(): Promise { + const expiredMutings = await this.findExpiredMutings(); + await this.channelMutingRepository.delete({ id: In(expiredMutings.map(x => x.id)) }); + + const userIds = [...new Set(expiredMutings.map(x => x.userId))]; + for (const userId of userIds) { + this.mutingChannelsCache.refresh(userId).then(); + } + } + + @bindThis + private async onMessage(_: string, data: string): Promise { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + switch (type) { + case 'muteChannel': { + this.mutingChannelsCache.refresh(body.userId).then(); + break; + } + case 'unmuteChannel': { + this.mutingChannelsCache.delete(body.userId).then(); + break; + } + } + } + } + + @bindThis + public dispose(): void { + this.mutingChannelsCache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } +} diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index a30bff0fe4..8c8d22c77d 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -15,6 +15,7 @@ import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { UserSearchService } from '@/core/UserSearchService.js'; import { WebhookTestService } from '@/core/WebhookTestService.js'; import { FlashService } from '@/core/FlashService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { AccountMoveService } from './AccountMoveService.js'; import { AccountUpdateService } from './AccountUpdateService.js'; import { AiService } from './AiService.js'; @@ -225,6 +226,7 @@ const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: Fe const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService }; const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService }; const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService }; +const $ChannelMutingService: Provider = { provide: 'ChannelMutingService', useExisting: ChannelMutingService }; const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService }; const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService }; const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService }; @@ -378,6 +380,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FanoutTimelineService, FanoutTimelineEndpointService, ChannelFollowingService, + ChannelMutingService, ChatService, RegistryApiService, ReversiService, @@ -527,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FanoutTimelineService, $FanoutTimelineEndpointService, $ChannelFollowingService, + $ChannelMutingService, $ChatService, $RegistryApiService, $ReversiService, @@ -677,6 +681,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FanoutTimelineService, FanoutTimelineEndpointService, ChannelFollowingService, + ChannelMutingService, ChatService, RegistryApiService, ReversiService, @@ -824,6 +829,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FanoutTimelineService, $FanoutTimelineEndpointService, $ChannelFollowingService, + $ChannelMutingService, $ChatService, $RegistryApiService, $ReversiService, diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 94c5691bf4..e39d70d683 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -19,6 +19,8 @@ import { isQuote, isRenote } from '@/misc/is-renote.js'; import { CacheService } from '@/core/CacheService.js'; import { isReply } from '@/misc/is-reply.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { isChannelRelated } from '@/misc/is-channel-related.js'; type NoteFilter = (note: MiNote) => boolean; @@ -35,6 +37,7 @@ type TimelineOptions = { ignoreAuthorFromBlock?: boolean; ignoreAuthorFromMute?: boolean; ignoreAuthorFromInstanceBlock?: boolean; + ignoreAuthorChannelFromMute?: boolean; excludeNoFiles?: boolean; excludeReplies?: boolean; excludePureRenotes: boolean; @@ -55,6 +58,7 @@ export class FanoutTimelineEndpointService { private cacheService: CacheService, private fanoutTimelineService: FanoutTimelineService, private utilityService: UtilityService, + private channelMutingService: ChannelMutingService, ) { } @@ -111,11 +115,13 @@ export class FanoutTimelineEndpointService { userIdsWhoMeMutingRenotes, userIdsWhoBlockingMe, userMutedInstances, + userMutedChannels, ] = await Promise.all([ this.cacheService.userMutingsCache.fetch(ps.me.id), this.cacheService.renoteMutingsCache.fetch(ps.me.id), this.cacheService.userBlockedCache.fetch(ps.me.id), this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)), + this.channelMutingService.mutingChannelsCache.fetch(me.id), ]); const parentFilter = filter; @@ -126,6 +132,7 @@ export class FanoutTimelineEndpointService { if (isUserRelated(note.renote, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false; if (isInstanceMuted(note, userMutedInstances)) return false; + if (isChannelRelated(note, userMutedChannels, ps.ignoreAuthorChannelFromMute)) return false; return parentFilter(note); }; diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 3215b41c8d..f4c747b139 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -255,6 +255,8 @@ export interface InternalEventTypes { metaUpdated: { before?: MiMeta; after: MiMeta; }; followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; + muteChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; + unmuteChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; updateUserProfile: MiUserProfile; mute: { muterId: MiUser['id']; muteeId: MiUser['id']; }; unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; }; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index b6acf4c5fb..748f2cbad9 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -604,6 +604,7 @@ export class NoteCreateService implements OnApplicationShutdown { replyUserHost: data.reply ? data.reply.userHost : null, renoteUserId: data.renote ? data.renote.userId : null, renoteUserHost: data.renote ? data.renote.userHost : null, + renoteChannelId: data.renote ? data.renote.channelId : null, userHost: user.host, }); diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 6714bda9a9..b112912b1b 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -106,6 +106,7 @@ function generateDummyNote(override?: Partial): MiNote { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, ...override, }; } diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 1ba7ca8e57..9ee918bea3 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -4,36 +4,40 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NotesRepository } from '@/models/_.js'; +import type { + ChannelFavoritesRepository, + ChannelFollowingsRepository, ChannelMutingRepository, + ChannelsRepository, + DriveFilesRepository, + MiDriveFile, + MiNote, + NotesRepository, +} from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiChannel } from '@/models/Channel.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; -import { In } from 'typeorm'; @Injectable() export class ChannelEntityService { constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, - @Inject(DI.channelFollowingsRepository) private channelFollowingsRepository: ChannelFollowingsRepository, - @Inject(DI.channelFavoritesRepository) private channelFavoritesRepository: ChannelFavoritesRepository, - + @Inject(DI.channelMutingRepository) + private channelMutingRepository: ChannelMutingRepository, @Inject(DI.notesRepository) private notesRepository: NotesRepository, - @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private noteEntityService: NoteEntityService, private driveFileEntityService: DriveFileEntityService, private idService: IdService, @@ -45,31 +49,59 @@ export class ChannelEntityService { src: MiChannel['id'] | MiChannel, me?: { id: MiUser['id'] } | null | undefined, detailed?: boolean, + opts?: { + bannerFiles?: Map; + followings?: Set; + favorites?: Set; + muting?: Set; + pinnedNotes?: Map; + }, ): Promise> { const channel = typeof src === 'object' ? src : await this.channelsRepository.findOneByOrFail({ id: src }); - const meId = me ? me.id : null; - const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null; + let bannerFile: MiDriveFile | null = null; + if (channel.bannerId) { + bannerFile = opts?.bannerFiles?.get(channel.bannerId) + ?? await this.driveFilesRepository.findOneByOrFail({ id: channel.bannerId }); + } - const isFollowing = meId ? await this.channelFollowingsRepository.exists({ - where: { - followerId: meId, - followeeId: channel.id, - }, - }) : false; + let isFollowing = false; + let isFavorited = false; + let isMuting = false; + if (me) { + isFollowing = opts?.followings?.has(channel.id) ?? await this.channelFollowingsRepository.exists({ + where: { + followerId: me.id, + followeeId: channel.id, + }, + }); - const isFavorited = meId ? await this.channelFavoritesRepository.exists({ - where: { - userId: meId, - channelId: channel.id, - }, - }) : false; + isFavorited = opts?.favorites?.has(channel.id) ?? await this.channelFavoritesRepository.exists({ + where: { + userId: me.id, + channelId: channel.id, + }, + }); - const pinnedNotes = channel.pinnedNoteIds.length > 0 ? await this.notesRepository.find({ - where: { - id: In(channel.pinnedNoteIds), - }, - }) : []; + isMuting = opts?.muting?.has(channel.id) ?? await this.channelMutingRepository.exists({ + where: { + userId: me.id, + channelId: channel.id, + }, + }); + } + + const pinnedNotes = Array.of(); + if (channel.pinnedNoteIds.length > 0) { + pinnedNotes.push( + ...( + opts?.pinnedNotes + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ? channel.pinnedNoteIds.map(it => opts.pinnedNotes!.get(it)).filter(it => it != null) + : await this.notesRepository.findBy({ id: In(channel.pinnedNoteIds) }) + ), + ); + } return { id: channel.id, @@ -78,7 +110,7 @@ export class ChannelEntityService { name: channel.name, description: channel.description, userId: channel.userId, - bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null, + bannerUrl: bannerFile ? this.driveFileEntityService.getPublicUrl(bannerFile) : null, pinnedNoteIds: channel.pinnedNoteIds, color: channel.color, isArchived: channel.isArchived, @@ -90,6 +122,7 @@ export class ChannelEntityService { ...(me ? { isFollowing, isFavorited, + isMuting, hasUnreadNote: false, // 後方互換性のため } : {}), @@ -98,5 +131,72 @@ export class ChannelEntityService { } : {}), }; } + + @bindThis + public async packMany( + src: MiChannel['id'][] | MiChannel[], + me?: { id: MiUser['id'] } | null | undefined, + detailed?: boolean, + ): Promise[]> { + // IDのみの要素がある場合、DBからオブジェクトを取得して補う + const channels = src.filter(it => typeof it === 'object') as MiChannel[]; + channels.push( + ...(await this.channelsRepository.find({ + where: { + id: In(src.filter(it => typeof it !== 'object') as MiChannel['id'][]), + }, + })), + ); + channels.sort((a, b) => a.id.localeCompare(b.id)); + + const bannerFiles = await this.driveFilesRepository + .findBy({ + id: In(channels.map(it => it.bannerId).filter(it => it != null)), + }) + .then(it => new Map(it.map(it => [it.id, it]))); + + const followings = me + ? await this.channelFollowingsRepository + .findBy({ + followerId: me.id, + followeeId: In(channels.map(it => it.id)), + }) + .then(it => new Set(it.map(it => it.followeeId))) + : new Set(); + + const favorites = me + ? await this.channelFavoritesRepository + .findBy({ + userId: me.id, + channelId: In(channels.map(it => it.id)), + }) + .then(it => new Set(it.map(it => it.channelId))) + : new Set(); + + const muting = me + ? await this.channelMutingRepository + .findBy({ + userId: me.id, + channelId: In(channels.map(it => it.id)), + }) + .then(it => new Set(it.map(it => it.channelId))) + : new Set(); + + const pinnedNotes = await this.notesRepository + .find({ + where: { + id: In(channels.flatMap(it => it.pinnedNoteIds)), + }, + }) + .then(it => new Map(it.map(it => [it.id, it]))); + + return Promise.all(channels.map(it => this.pack(it, me, detailed, { + bannerFiles, + followings, + favorites, + muting, + pinnedNotes, + }))); + } } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index c915133453..b9ca76233c 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -70,6 +70,7 @@ export const DI = { channelsRepository: Symbol('channelsRepository'), channelFollowingsRepository: Symbol('channelFollowingsRepository'), channelFavoritesRepository: Symbol('channelFavoritesRepository'), + channelMutingRepository: Symbol('channelMutingRepository'), registryItemsRepository: Symbol('registryItemsRepository'), webhooksRepository: Symbol('webhooksRepository'), systemWebhooksRepository: Symbol('systemWebhooksRepository'), diff --git a/packages/backend/src/misc/is-channel-related.ts b/packages/backend/src/misc/is-channel-related.ts new file mode 100644 index 0000000000..fef736dad6 --- /dev/null +++ b/packages/backend/src/misc/is-channel-related.ts @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { MiNote } from '@/models/Note.js'; +import { Packed } from '@/misc/json-schema.js'; + +/** + * {@link note}が{@link channelIds}のチャンネルに関連するかどうかを判定し、関連する場合はtrueを返します。 + * 関連するというのは、{@link channelIds}のチャンネルに向けての投稿であるか、またはそのチャンネルの投稿をリノート・引用リノートした投稿であるかを指します。 + * + * @param note 確認対象のノート + * @param channelIds 確認対象のチャンネルID一覧 + * @param ignoreAuthor trueの場合、ノートの所属チャンネルが{@link channelIds}に含まれていても無視します(デフォルトはfalse) + */ +export function isChannelRelated(note: MiNote | Packed<'Note'>, channelIds: Set, ignoreAuthor = false): boolean { + // ノートの所属チャンネルが確認対象のチャンネルID一覧に含まれている場合 + if (!ignoreAuthor && note.channelId && channelIds.has(note.channelId)) { + return true; + } + + const renoteChannelId = note.renote?.channelId; + if (renoteChannelId != null && renoteChannelId !== note.channelId && channelIds.has(renoteChannelId)) { + return true; + } + + // NOTE: リプライはchannelIdのチェックだけでOKなはずなので見てない(チャンネルのノートにチャンネル外からのリプライまたはその逆はないはずなので) + + return false; +} diff --git a/packages/backend/src/models/ChannelMuting.ts b/packages/backend/src/models/ChannelMuting.ts new file mode 100644 index 0000000000..11ac7e5cef --- /dev/null +++ b/packages/backend/src/models/ChannelMuting.ts @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiChannel } from './Channel.js'; + +@Entity('channel_muting') +@Index(['userId', 'channelId'], {}) +export class MiChannelMuting { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column({ + ...id(), + }) + public userId: MiUser['id']; + + @ManyToOne(type => MiUser, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: MiUser | null; + + @Index() + @Column({ + ...id(), + }) + public channelId: MiChannel['id']; + + @ManyToOne(type => MiChannel, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public channel: MiChannel | null; + + @Index() + @Column('timestamp with time zone', { + nullable: true, + }) + public expiresAt: Date | null; +} diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 26d5c1d535..23e5960b60 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -248,6 +248,14 @@ export class MiNote { }) public renoteUserHost: string | null; + @Column({ + ...id(), + nullable: true, + comment: '[Denormalized]', + }) + public renoteChannelId: MiChannel['id'] | null; + //#endregion + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 146dbbc3b8..e3db6f8838 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -21,6 +21,7 @@ import { MiChannel, MiChannelFavorite, MiChannelFollowing, + MiChannelMuting, MiClip, MiClipFavorite, MiClipNote, @@ -429,6 +430,12 @@ const $channelFavoritesRepository: Provider = { inject: [DI.db], }; +const $channelMutingRepository: Provider = { + provide: DI.channelMutingRepository, + useFactory: (db: DataSource) => db.getRepository(MiChannelMuting).extend(miRepository as MiRepository), + inject: [DI.db], +}; + const $registryItemsRepository: Provider = { provide: DI.registryItemsRepository, useFactory: (db: DataSource) => db.getRepository(MiRegistryItem).extend(miRepository as MiRepository), @@ -597,6 +604,7 @@ const $reversiGamesRepository: Provider = { $channelsRepository, $channelFollowingsRepository, $channelFavoritesRepository, + $channelMutingRepository, $registryItemsRepository, $webhooksRepository, $systemWebhooksRepository, @@ -674,6 +682,7 @@ const $reversiGamesRepository: Provider = { $channelsRepository, $channelFollowingsRepository, $channelFavoritesRepository, + $channelMutingRepository, $registryItemsRepository, $webhooksRepository, $systemWebhooksRepository, diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 84b5cbed0a..8c77a225c8 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -32,6 +32,7 @@ import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiChannel } from '@/models/Channel.js'; import { MiChannelFavorite } from '@/models/ChannelFavorite.js'; import { MiChannelFollowing } from '@/models/ChannelFollowing.js'; +import { MiChannelMuting } from "@/models/ChannelMuting.js"; import { MiChatApproval } from '@/models/ChatApproval.js'; import { MiChatMessage } from '@/models/ChatMessage.js'; import { MiChatRoom } from '@/models/ChatRoom.js'; @@ -172,6 +173,7 @@ export { MiBlocking, MiChannelFollowing, MiChannelFavorite, + MiChannelMuting, MiClip, MiClipNote, MiClipFavorite, @@ -251,6 +253,7 @@ export type AuthSessionsRepository = Repository & MiRepository & MiRepository; export type ChannelFollowingsRepository = Repository & MiRepository; export type ChannelFavoritesRepository = Repository & MiRepository; +export type ChannelMutingRepository = Repository & MiRepository; export type ClipsRepository = Repository & MiRepository; export type ClipNotesRepository = Repository & MiRepository; export type ClipFavoritesRepository = Repository & MiRepository; diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts index d233f7858d..a7966ffdb3 100644 --- a/packages/backend/src/models/json-schema/channel.ts +++ b/packages/backend/src/models/json-schema/channel.ts @@ -80,6 +80,10 @@ export const packedChannelSchema = { type: 'boolean', optional: true, nullable: false, }, + isMuting: { + type: 'boolean', + optional: true, nullable: false, + }, pinnedNotes: { type: 'array', optional: true, nullable: false, diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index f6cbbbe64c..a3e659c110 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -25,6 +25,7 @@ import { MiAuthSession } from '@/models/AuthSession.js'; import { MiBlocking } from '@/models/Blocking.js'; import { MiChannelFollowing } from '@/models/ChannelFollowing.js'; import { MiChannelFavorite } from '@/models/ChannelFavorite.js'; +import { MiChannelMuting } from "@/models/ChannelMuting.js"; import { MiClip } from '@/models/Clip.js'; import { MiClipNote } from '@/models/ClipNote.js'; import { MiClipFavorite } from '@/models/ClipFavorite.js'; @@ -239,6 +240,7 @@ export const entities = [ MiChannel, MiChannelFollowing, MiChannelFavorite, + MiChannelMuting, MiRegistryItem, MiAd, MiPasswordResetRequest, diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 448fc9c763..e898e6dd48 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -4,14 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MutingsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { UserMutingService } from '@/core/UserMutingService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type * as Bull from 'bullmq'; @Injectable() export class CheckExpiredMutingsProcessorService { @@ -22,6 +21,7 @@ export class CheckExpiredMutingsProcessorService { private mutingsRepository: MutingsRepository, private userMutingService: UserMutingService, + private channelMutingService: ChannelMutingService, private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); @@ -41,6 +41,8 @@ export class CheckExpiredMutingsProcessorService { await this.userMutingService.unmute(expired); } + await this.channelMutingService.eraseExpiredMutings(); + this.logger.succ('All expired mutings checked.'); } } diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 2a4e1fc574..21f2f0b7e2 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -15,6 +15,7 @@ import { CacheService } from '@/core/CacheService.js'; import { MiLocalUser } from '@/models/User.js'; import { UserService } from '@/core/UserService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; @@ -39,6 +40,7 @@ export class StreamingApiServerService { private notificationService: NotificationService, private usersService: UserService, private channelFollowingService: ChannelFollowingService, + private channelMutingService: ChannelMutingService, ) { } @@ -97,6 +99,7 @@ export class StreamingApiServerService { this.notificationService, this.cacheService, this.channelFollowingService, + this.channelMutingService, user, app, ); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index f4aa07875d..9aecc0f0fd 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -143,6 +143,9 @@ export * as 'channels/timeline' from './endpoints/channels/timeline.js'; export * as 'channels/unfavorite' from './endpoints/channels/unfavorite.js'; export * as 'channels/unfollow' from './endpoints/channels/unfollow.js'; export * as 'channels/update' from './endpoints/channels/update.js'; +export * as 'channels/mute/create' from './endpoints/channels/mute/create.js'; +export * as 'channels/mute/delete' from './endpoints/channels/mute/delete.js'; +export * as 'channels/mute/list' from './endpoints/channels/mute/list.js'; export * as 'charts/active-users' from './endpoints/charts/active-users.js'; export * as 'charts/ap-request' from './endpoints/charts/ap-request.js'; export * as 'charts/drive' from './endpoints/charts/drive.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index b2d9cea03c..c59479d370 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; +import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, AntennasRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; @@ -14,6 +15,7 @@ import { IdService } from '@/core/IdService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { trackPromise } from '@/misc/promise-tracker.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -69,6 +71,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private fanoutTimelineService: FanoutTimelineService, private globalEventService: GlobalEventService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -108,6 +111,21 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); + // -- ミュートされたチャンネル対策 + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + if (mutingChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL'); + qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + } + // NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。 // https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255 diff --git a/packages/backend/src/server/api/endpoints/channels/mute/create.ts b/packages/backend/src/server/api/endpoints/channels/mute/create.ts new file mode 100644 index 0000000000..26ce707c7a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/channels/mute/create.ts @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { ChannelsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; + +export const meta = { + tags: ['channels', 'mute'], + + requireCredential: true, + prohibitMoved: true, + + kind: 'write:channels', + + errors: { + noSuchChannel: { + message: 'No such Channel.', + code: 'NO_SUCH_CHANNEL', + id: '7174361e-d58f-31d6-2e7c-6fb830786a3f', + }, + + alreadyMuting: { + message: 'You are already muting that user.', + code: 'ALREADY_MUTING_CHANNEL', + id: '5a251978-769a-da44-3e89-3931e43bb592', + }, + + expiresAtIsPast: { + message: 'Cannot set past date to "expiresAt".', + code: 'EXPIRES_AT_IS_PAST', + id: '42b32236-df2c-a45f-fdbf-def67268f749', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + channelId: { type: 'string', format: 'misskey:id' }, + expiresAt: { + type: 'integer', + nullable: true, + description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.', + }, + }, + required: ['channelId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + private channelMutingService: ChannelMutingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Check if exists the channel + const targetChannel = await this.channelsRepository.findOneBy({ id: ps.channelId }); + if (!targetChannel) { + throw new ApiError(meta.errors.noSuchChannel); + } + + // Check if already muting + const exist = await this.channelMutingService.isMuted({ + requestUserId: me.id, + targetChannelId: targetChannel.id, + }); + if (exist) { + throw new ApiError(meta.errors.alreadyMuting); + } + + // Check if expiresAt is past + if (ps.expiresAt && ps.expiresAt <= Date.now()) { + throw new ApiError(meta.errors.expiresAtIsPast); + } + + await this.channelMutingService.mute({ + requestUserId: me.id, + targetChannelId: targetChannel.id, + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/mute/delete.ts b/packages/backend/src/server/api/endpoints/channels/mute/delete.ts new file mode 100644 index 0000000000..79abeebe99 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/channels/mute/delete.ts @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { ChannelsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['channels', 'mute'], + + requireCredential: true, + prohibitMoved: true, + + kind: 'write:channels', + + errors: { + noSuchChannel: { + message: 'No such Channel.', + code: 'NO_SUCH_CHANNEL', + id: 'e7998769-6e94-d9c2-6b8f-94a527314aba', + }, + + notMuting: { + message: 'You are not muting that channel.', + code: 'NOT_MUTING_CHANNEL', + id: '14d55962-6ea8-d990-1333-d6bef78dc2ab', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + channelId: { type: 'string', format: 'misskey:id' }, + }, + required: ['channelId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + private channelMutingService: ChannelMutingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Check if exists the channel + const targetChannel = await this.channelsRepository.findOneBy({ id: ps.channelId }); + if (!targetChannel) { + throw new ApiError(meta.errors.noSuchChannel); + } + + // Check muting + const exist = await this.channelMutingService.isMuted({ + requestUserId: me.id, + targetChannelId: targetChannel.id, + }); + if (!exist) { + throw new ApiError(meta.errors.notMuting); + } + + await this.channelMutingService.unmute({ + requestUserId: me.id, + targetChannelId: targetChannel.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/mute/list.ts b/packages/backend/src/server/api/endpoints/channels/mute/list.ts new file mode 100644 index 0000000000..74338eea38 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/channels/mute/list.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; + +export const meta = { + tags: ['channels', 'mute'], + + requireCredential: true, + prohibitMoved: true, + + kind: 'read:channels', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Channel', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private channelMutingService: ChannelMutingService, + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const mutings = await this.channelMutingService.list({ + requestUserId: me.id, + }); + return await this.channelEntityService.packMany(mutings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 46b050d4b4..4f56bc2110 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { MiLocalUser } from '@/models/User.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -70,6 +71,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private activeUsersChart: ActiveUsersChart, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -98,6 +100,7 @@ export default class extends Endpoint { // eslint- useDbFallback: true, redisTimelines: [`channelTimeline:${channel.id}`], excludePureRenotes: false, + ignoreAuthorChannelFromMute: true, dbFallback: async (untilId, sinceId, limit) => { return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id }, me); }, @@ -122,6 +125,16 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); this.queryService.generateBaseNoteFilteringQuery(query, me); + + if (me) { + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id).filter(x => x !== ps.channelId)); + if (mutingChannelIds.length > 0) { + query.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + query.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + } + } //#endregion return await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 2c8459525a..0a3602df20 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -18,6 +18,8 @@ import { QueryService } from '@/core/QueryService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -46,7 +48,7 @@ export const meta = { bothWithRepliesAndWithFiles: { message: 'Specifying both withReplies and withFiles is not supported', code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', - id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f' + id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f', }, }, } as const; @@ -79,9 +81,6 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, - @Inject(DI.channelFollowingsRepository) - private channelFollowingsRepository: ChannelFollowingsRepository, - private noteEntityService: NoteEntityService, private roleService: RoleService, private activeUsersChart: ActiveUsersChart, @@ -89,6 +88,8 @@ export default class extends Endpoint { // eslint- private cacheService: CacheService, private queryService: QueryService, private userFollowingService: UserFollowingService, + private channelMutingService: ChannelMutingService, + private channelFollowingService: ChannelFollowingService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, ) { super(meta, paramDef, async (ps, me) => { @@ -196,11 +197,13 @@ export default class extends Endpoint { // eslint- withReplies: boolean, }, me: MiLocalUser) { const followees = await this.userFollowingService.getFollowees(me.id); - const followingChannels = await this.channelFollowingsRepository.find({ - where: { - followerId: me.id, - }, - }); + + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + const followingChannelIds = await this.channelFollowingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id).filter(x => !mutingChannelIds.includes(x))); const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { @@ -219,9 +222,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - if (followingChannels.length > 0) { - const followingChannelIds = followingChannels.map(x => x.followeeId); - + if (followingChannelIds.length > 0) { query.andWhere(new Brackets(qb => { qb.where('note.channelId IN (:...followingChannelIds)', { followingChannelIds }); qb.orWhere('note.channelId IS NULL'); @@ -230,6 +231,13 @@ export default class extends Endpoint { // eslint- query.andWhere('note.channelId IS NULL'); } + if (mutingChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL'); + qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + } + if (!ps.withReplies) { query.andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index ee61ab43da..ec9e52cf04 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -15,6 +15,7 @@ import { IdService } from '@/core/IdService.js'; import { QueryService } from '@/core/QueryService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -76,6 +77,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private queryService: QueryService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -157,7 +159,19 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBaseNoteFilteringQuery(query, me); - if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + if (me) { + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + + const mutedChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + if (mutedChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL') + .orWhere('note.renoteChannelId NOT IN (:...mutedChannelIds)', { mutedChannelIds }); + })); + } + } if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index eeeb797efc..fe9c412be4 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js'; +import type { NotesRepository, MiMeta } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -16,6 +16,8 @@ import { CacheService } from '@/core/CacheService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; export const meta = { tags: ['notes'], @@ -61,15 +63,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, - @Inject(DI.channelFollowingsRepository) - private channelFollowingsRepository: ChannelFollowingsRepository, - private noteEntityService: NoteEntityService, private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private userFollowingService: UserFollowingService, + private channelMutingService: ChannelMutingService, + private channelFollowingService: ChannelFollowingService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { @@ -140,11 +141,13 @@ export default class extends Endpoint { // eslint- private async getFromDb(ps: { untilId: string | null; sinceId: string | null; limit: number; includeMyRenotes: boolean; includeRenotedMyNotes: boolean; includeLocalRenotes: boolean; withFiles: boolean; withRenotes: boolean; }, me: MiLocalUser) { const followees = await this.userFollowingService.getFollowees(me.id); - const followingChannels = await this.channelFollowingsRepository.find({ - where: { - followerId: me.id, - }, - }); + + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + const followingChannelIds = await this.channelFollowingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id).filter(x => !mutingChannelIds.includes(x))); //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) @@ -154,15 +157,14 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - if (followees.length > 0 && followingChannels.length > 0) { + if (followees.length > 0 && followingChannelIds.length > 0) { // ユーザー・チャンネルともにフォローあり const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; - const followingChannelIds = followingChannels.map(x => x.followeeId); query.andWhere(new Brackets(qb => { qb .where(new Brackets(qb2 => { qb2 - .where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }) + .andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }) .andWhere('note.channelId IS NULL'); })) .orWhere('note.channelId IN (:...followingChannelIds)', { followingChannelIds }); @@ -170,22 +172,32 @@ export default class extends Endpoint { // eslint- } else if (followees.length > 0) { // ユーザーフォローのみ(チャンネルフォローなし) const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; - query - .andWhere('note.channelId IS NULL') - .andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); - } else if (followingChannels.length > 0) { - // チャンネルフォローのみ(ユーザーフォローなし) - const followingChannelIds = followingChannels.map(x => x.followeeId); query.andWhere(new Brackets(qb => { qb + .andWhere('note.channelId IS NULL') + .andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); + if (mutingChannelIds.length > 0) { + qb.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + } + })); + } else if (followingChannelIds.length > 0) { + // チャンネルフォローのみ(ユーザーフォローなし) + query.andWhere(new Brackets(qb => { + qb + // renoteChannelIdは見る必要が無い + // ・HTLに流れてくるチャンネル=フォローしているチャンネル + // ・HTLにフォロー外のチャンネルが流れるのは、フォローしているユーザがそのチャンネル投稿をリノートした場合のみ + // つまり、ユーザフォローしてない前提のこのブロックでは見る必要が無い .where('note.channelId IN (:...followingChannelIds)', { followingChannelIds }) .orWhere('note.userId = :meId', { meId: me.id }); })); } else { // フォローなし - query - .andWhere('note.channelId IS NULL') - .andWhere('note.userId = :meId', { meId: me.id }); + query.andWhere(new Brackets(qb => { + qb + .andWhere('note.channelId IS NULL') + .andWhere('note.userId = :meId', { meId: me.id }); + })); } query.andWhere(new Brackets(qb => { diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 42e80c6ae1..c0c8653f7b 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js'; import { QueryService } from '@/core/QueryService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -84,6 +85,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private queryService: QueryService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -187,6 +189,17 @@ export default class extends Endpoint { // eslint- this.queryService.generateBaseNoteFilteringQuery(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + // -- ミュートされたチャンネルのリノート対策 + const mutedChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + if (mutedChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL') + .orWhere('note.renoteChannelId NOT IN (:...mutedChannelIds)', { mutedChannelIds }); + })); + } + if (ps.includeMyRenotes === false) { query.andWhere(new Brackets(qb => { qb.orWhere('note.userId != :meId', { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index e8a760e9f8..4515c016a8 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; +import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, RolesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; @@ -12,6 +13,7 @@ import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -68,6 +70,7 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private queryService: QueryService, private fanoutTimelineService: FanoutTimelineService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -101,6 +104,21 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); + // -- ミュートされたチャンネル対策 + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)); + if (mutingChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL'); + qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + } + this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBaseNoteFilteringQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 5832790a61..b9710250cf 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -16,6 +16,7 @@ import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { FanoutTimelineName } from '@/core/FanoutTimelineService.js'; import { ApiError } from '@/server/api/error.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; export const meta = { tags: ['users', 'notes'], @@ -77,12 +78,12 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, - private noteEntityService: NoteEntityService, private queryService: QueryService, private cacheService: CacheService, private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -165,6 +166,11 @@ export default class extends Endpoint { // eslint- withFiles: boolean, withRenotes: boolean, }, me: MiLocalUser | null) { + const mutingChannelIds = me + ? await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)) + : []; const isSelf = me && (me.id === ps.userId); const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) @@ -177,14 +183,30 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.withChannelNotes) { - if (!isSelf) query.andWhere(new Brackets(qb => { - qb.orWhere('note.channelId IS NULL'); - qb.orWhere('channel.isSensitive = false'); + query.andWhere(new Brackets(qb => { + if (mutingChannelIds.length > 0) { + qb.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds: mutingChannelIds }); + } + + if (!isSelf) { + qb.andWhere(new Brackets(qb2 => { + qb2.orWhere('note.channelId IS NULL'); + qb2.orWhere('channel.isSensitive = false'); + })); + } })); } else { query.andWhere('note.channelId IS NULL'); } + // -- ミュートされたチャンネルのリノート対策 + if (mutingChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL'); + qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + } + this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBaseNoteFilteringQuery(query, me, { excludeAuthor: true, diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index 8e28ab263b..222086c960 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -11,8 +11,9 @@ import type { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { MiFollowing, MiUserProfile } from '@/models/_.js'; -import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js'; +import type { GlobalEvents, StreamEventEmitter } from '@/core/GlobalEventService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type { ChannelsService } from './ChannelsService.js'; @@ -35,6 +36,7 @@ export default class Connection { public userProfile: MiUserProfile | null = null; public following: Record | undefined> = {}; public followingChannels: Set = new Set(); + public mutingChannels: Set = new Set(); public userIdsWhoMeMuting: Set = new Set(); public userIdsWhoBlockingMe: Set = new Set(); public userIdsWhoMeMutingRenotes: Set = new Set(); @@ -46,7 +48,7 @@ export default class Connection { private notificationService: NotificationService, private cacheService: CacheService, private channelFollowingService: ChannelFollowingService, - + private channelMutingService: ChannelMutingService, user: MiUser | null | undefined, token: MiAccessToken | null | undefined, ) { @@ -57,10 +59,19 @@ export default class Connection { @bindThis public async fetch() { if (this.user == null) return; - const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes] = await Promise.all([ + const [ + userProfile, + following, + followingChannels, + mutingChannels, + userIdsWhoMeMuting, + userIdsWhoBlockingMe, + userIdsWhoMeMutingRenotes, + ] = await Promise.all([ this.cacheService.userProfileCache.fetch(this.user.id), this.cacheService.userFollowingsCache.fetch(this.user.id), this.channelFollowingService.userFollowingChannelsCache.fetch(this.user.id), + this.channelMutingService.mutingChannelsCache.fetch(this.user.id), this.cacheService.userMutingsCache.fetch(this.user.id), this.cacheService.userBlockedCache.fetch(this.user.id), this.cacheService.renoteMutingsCache.fetch(this.user.id), @@ -68,6 +79,7 @@ export default class Connection { this.userProfile = userProfile; this.following = following; this.followingChannels = followingChannels; + this.mutingChannels = mutingChannels; this.userIdsWhoMeMuting = userIdsWhoMeMuting; this.userIdsWhoBlockingMe = userIdsWhoBlockingMe; this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 686aea423c..465ed4238c 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -6,7 +6,8 @@ import { bindThis } from '@/decorators.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; +import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; +import { isChannelRelated } from '@/misc/is-channel-related.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type Connection from './Connection.js'; @@ -55,6 +56,10 @@ export default abstract class Channel { return this.connection.followingChannels; } + protected get mutingChannels() { + return this.connection.mutingChannels; + } + protected get subscriber() { return this.connection.subscriber; } @@ -74,6 +79,9 @@ export default abstract class Channel { // 流れてきたNoteがリノートをミュートしてるユーザが行ったもの if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true; + // 流れてきたNoteがミュートしているチャンネルと関わる + if (isChannelRelated(note, this.mutingChannels)) return true; + return false; } diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index ac79c31854..9a0da5b66b 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -8,6 +8,8 @@ import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; +import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +import { isUserRelated } from '@/misc/is-user-related.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type MiChannelService } from '../channel.js'; @@ -19,7 +21,6 @@ class ChannelChannel extends Channel { constructor( private noteEntityService: NoteEntityService, - id: string, connection: Channel['connection'], ) { @@ -52,6 +53,35 @@ class ChannelChannel extends Channel { this.send('note', note); } + /* + * ミュートとブロックされてるを処理する + */ + protected override isNoteMutedOrBlocked(note: Packed<'Note'>): boolean { + // 流れてきたNoteがインスタンスミュートしたインスタンスが関わる + if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return true; + + // 流れてきたNoteがミュートしているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoMeMuting)) return true; + // 流れてきたNoteがブロックされているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoBlockingMe)) return true; + + // 流れてきたNoteがリノートをミュートしてるユーザが行ったもの + if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true; + + // このソケットで見ているチャンネルがミュートされていたとしても、チャンネルを直接見ている以上は流すようにしたい + // ただし、他のミュートしているチャンネルは流さないようにもしたい + // ノート自体のチャンネルIDはonNoteでチェックしているので、ここではリノートのチャンネルIDをチェックする + if ( + (note.renote) && + (note.renote.channelId !== this.channelId) && + (note.renote.channelId && this.mutingChannels.has(note.renote.channelId)) + ) { + return true; + } + + return false; + } + @bindThis public dispose() { // Unsubscribe events diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 157d9fc279..eb5b4a8c6c 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -44,7 +44,10 @@ class HomeTimelineChannel extends Channel { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; if (note.channelId) { - if (!this.followingChannels.has(note.channelId)) return; + // そのチャンネルをフォローしていない + if (!this.followingChannels.has(note.channelId)) { + return; + } } else { // その投稿のユーザーをフォローしていなかったら弾く if (!isMe && !Object.hasOwn(this.following, note.userId)) return; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index db5b4576be..2155e02012 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -53,16 +53,25 @@ class HybridTimelineChannel extends Channel { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; - // チャンネルの投稿ではなく、自分自身の投稿 または - // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または - // チャンネルの投稿ではなく、全体公開のローカルの投稿 または - // フォローしているチャンネルの投稿 の場合だけ - if (!( - (note.channelId == null && isMe) || - (note.channelId == null && Object.hasOwn(this.following, note.userId)) || - (note.channelId == null && (note.user.host == null && note.visibility === 'public')) || - (note.channelId != null && this.followingChannels.has(note.channelId)) - )) return; + if (!note.channelId) { + // 以下の条件に該当するノートのみ後続処理に通す(ので、以下のif文は該当しないノートをすべて弾くようにする) + // - 自分自身の投稿 + // - その投稿のユーザーをフォローしている + // - 全体公開のローカルの投稿 + if (!( + isMe || + Object.hasOwn(this.following, note.userId) || + (note.user.host == null && note.visibility === 'public') + )) { + return; + } + } else { + // 以下の条件に該当するノートのみ後続処理に通す(ので、以下のif文は該当しないノートをすべて弾くようにする) + // - フォローしているチャンネルの投稿 + if (!this.followingChannels.has(note.channelId)) { + return; + } + } if (note.visibility === 'followers') { if (!isMe && !Object.hasOwn(this.following, note.userId)) return; diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 1bbacd065b..70a5c9579e 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -69,6 +69,9 @@ describe('アンテナ', () => { let userMutingAlice: User; let userMutedByAlice: User; + let testChannel: misskey.entities.Channel; + let testMutedChannel: misskey.entities.Channel; + beforeAll(async () => { root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); @@ -120,6 +123,10 @@ describe('アンテナ', () => { userMutedByAlice = await signup({ username: 'userMutedByAlice' }); await post(userMutedByAlice, { text: 'test' }); await api('mute/create', { userId: userMutedByAlice.id }, alice); + + testChannel = (await api('channels/create', { name: 'test' }, root)).body; + testMutedChannel = (await api('channels/create', { name: 'test-muted' }, root)).body; + await api('channels/mute/create', { channelId: testMutedChannel.id }, alice); }, 1000 * 60 * 10); beforeEach(async () => { @@ -605,6 +612,20 @@ describe('アンテナ', () => { { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, ], }, + { + label: 'チャンネルノートも含む', + parameters: () => ({ src: 'all' }), + posts: [ + { note: (): Promise => post(bob, { text: `test ${keyword}`, channelId: testChannel.id }), included: true }, + ], + }, + { + label: 'ミュートしてるチャンネルは含まない', + parameters: () => ({ src: 'all' }), + posts: [ + { note: (): Promise => post(bob, { text: `test ${keyword}`, channelId: testMutedChannel.id }) }, + ], + }, ])('が取得できること($label)', async ({ parameters, posts }) => { const antenna = await successfulApiCall({ endpoint: 'antennas/create', diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 4f7d1a4d69..4fd826100d 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -3,14 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + // How to run: // pnpm jest -- e2e/timelines.ts import * as assert from 'assert'; import { setTimeout } from 'node:timers/promises'; +import { entities } from 'misskey-js'; import { Redis } from 'ioredis'; -import { SignupResponse, Note, UserList } from 'misskey-js/entities.js'; -import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js'; +import { SignupResponse, Note } from 'misskey-js/entities.js'; +import { api, initTestDb, post, randomString, sendEnvUpdateRequest, signup, uploadUrl, UserToken } from '../utils.js'; import { loadConfig } from '@/config.js'; function genHost() { @@ -20,12 +23,72 @@ function genHost() { let redisForTimelines: Redis; let root: SignupResponse; +async function renote(noteId: string, user: UserToken): Promise { + return await api('notes/create', { renoteId: noteId }, user).then(it => it.body.createdNote); +} + +async function createChannel(name: string, user: UserToken): Promise { + return (await api('channels/create', { name }, user)).body; +} + +async function followChannel(channelId: string, user: UserToken) { + return await api('channels/follow', { channelId }, user); +} + +async function muteChannel(channelId: string, user: UserToken) { + await api('channels/mute/create', { channelId }, user); +} + +async function createList(name: string, user: UserToken): Promise { + return (await api('users/lists/create', { name }, user)).body; +} + +async function pushList(listId: string, pushUserIds: string[] = [], user: UserToken) { + for (const userId of pushUserIds) { + await api('users/lists/push', { listId, userId }, user); + } + await setTimeout(500); +} + +async function createRole(name: string, user: UserToken): Promise { + return (await api('admin/roles/create', { + name, + description: '', + color: '#000000', + iconUrl: '', + target: 'manual', + condFormula: {}, + isPublic: true, + isModerator: false, + isAdministrator: false, + isExplorable: true, + asBadge: false, + canEditMembersByModerator: false, + displayOrder: 0, + policies: {}, + }, user)).body; +} + +async function assignRole(roleId: string, userId: string, user: UserToken) { + await api('admin/roles/assign', { userId, roleId }, user); +} + describe('Timelines', () => { + let root: UserToken; + beforeAll(async () => { redisForTimelines = new Redis(loadConfig().redisForTimelines); root = await signup({ username: 'root' }); }, 1000 * 60 * 2); + // afterEach(async () => { + // // テスト中に作ったノートをきれいにする。 + // // ユーザも作っているが、時間差で動く通知系処理などがあり、このタイミングで消すとエラー落ちするので消さない(ノートさえ消えていれば支障はない) + // const db = await initTestDb(true); + // await db.query('DELETE FROM "note"'); + // await db.query('DELETE FROM "channel"'); + // }); + describe.each([ { enableFanoutTimeline: true }, { enableFanoutTimeline: false }, @@ -611,6 +674,280 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); }); + describe('Channel', () => { + test('チャンネル未フォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('チャンネル未フォロー + ユーザフォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + }); + test('FTT: ローカルユーザーの HTL にはプッシュされる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); @@ -1012,41 +1349,277 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); }, 1000 * 10); - describe('凍結', () => { - let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; - let aliceNote: Note, bobNote: Note, carolNote: Note; + describe('Channel', () => { + test('チャンネル未フォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); - beforeAll(async () => { - [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + const channel = await createChannel('channel', bob); - aliceNote = await post(alice, { text: 'hi' }); - bobNote = await post(bob, { text: 'yo' }); - carolNote = await post(carol, { text: 'kon\'nichiwa' }); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); await waitForPushToTl(); - }); - - test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { - await api('admin/suspend-user', { userId: carol.id }, root); - await setTimeout(100); const res = await api('notes/local-timeline', { limit: 100 }, alice); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); - test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { - await api('admin/unsuspend-user', { userId: carol.id }, root); - await setTimeout(100); + test('チャンネルフォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); const res = await api('notes/local-timeline', { limit: 100 }, alice); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); - assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'kon\'nichiwa'); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + ユーザフォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); }); }); }); @@ -1091,7 +1664,8 @@ describe('Timelines', () => { }); test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { - /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; const [alice, bob] = await Promise.all([signup(), signup()]); @@ -1127,7 +1701,8 @@ describe('Timelines', () => { }); test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { - /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); @@ -1149,7 +1724,8 @@ describe('Timelines', () => { }); test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => { - /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; const [alice, bob] = await Promise.all([signup(), signup()]); @@ -1225,7 +1801,8 @@ describe('Timelines', () => { }); test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => { - /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; const [alice, bob] = await Promise.all([signup(), signup()]); @@ -1269,6 +1846,280 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); }, 1000 * 10); + describe('Channel', () => { + test('チャンネル未フォロー + ユーザ未フォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('チャンネル未フォロー + ユーザフォロー = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザ未フォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + ユーザフォロー + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + await api('following/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + }); + describe('凍結', () => { /* * bob = 未フォローのローカルユーザー (凍結対象でない) @@ -1607,45 +2458,309 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); }); - describe('凍結', () => { - let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; - let aliceNote: Note, bobNote: Note, carolNote: Note; - let list: UserList; + describe('Channel', () => { + test('チャンネル未フォロー + リスインしてない = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); - beforeAll(async () => { - [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + const list = await createList('list', alice); - list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + const channel = await createChannel('channel', bob); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('users/lists/push', { listId: list.id, userId: carol.id }, alice); - aliceNote = await post(alice, { text: 'hi' }); - bobNote = await post(bob, { text: 'yo' }); - carolNote = await post(carol, { text: 'kon\'nichiwa' }); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); await waitForPushToTl(); - await api('admin/suspend-user', { userId: carol.id }, root); - await setTimeout(100); + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); - test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + test('チャンネルフォロー + リスインしてない = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); - test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { - await api('admin/unsuspend-user', { userId: carol.id }, root); - await setTimeout(100); + test('チャンネル未フォロー + リスインしてる = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + リスインしてる = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + リスインしてない + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + リスインしてない + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネル未フォロー + リスインしてる + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('チャンネルフォロー + リスインしてる + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + リスインしてない = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + リスインしてない = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + リスインしてる = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルフォロー + リスインしてる = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + リスインしてない + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + リスインしてない + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネル未フォロー + リスインしてる + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + + test('[チャンネル外リノート] チャンネルフォロー + リスインしてる + チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await createList('list', alice); + await pushList(list.id, [bob.id], alice); + + const channel = await createChannel('channel', bob); + await followChannel(channel.id, alice); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { limit: 100, listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); }); }); }); @@ -1937,8 +3052,276 @@ describe('Timelines', () => { const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id, untilId: noteUntil.id }); assert.deepStrictEqual(res.body, [note3, note2, note1]); }); + + describe('Channel', () => { + test('チャンネルミュートなし = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('[チャンネル外リノート] チャンネルミュートなし = TLに流れる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('[チャンネル外リノート] チャンネルミュート = TLに流れない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await renote(bobNote.id, bob); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + }); }); + describe('Channel TL', () => { + test('閲覧中チャンネルのノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('閲覧中チャンネルとは別チャンネルのノートは含まれない', async() => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + const channel2 = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel2.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('閲覧中チャンネルのノートにリノートが含まれる', async() => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('閲覧中チャンネルとは別チャンネルからのリノートが含まれる', async() => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + const channel2 = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel2.id }); + const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('閲覧中チャンネルに自分の他人への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id, channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); + }); + + test('閲覧中チャンネルに他人の自分への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi', channelId: channel.id }); + const bobNote = await post(bob, { text: 'ok', replyId: aliceNote.id, channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('閲覧中チャンネルにミュートしているユーザのノートは含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('mute/create', { userId: bob.id }, alice); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('閲覧中チャンネルにこちらをブロックしているユーザのノートは含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('blocking/create', { userId: alice.id }, bob); + + const channel = await createChannel('channel', bob); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test('閲覧中チャンネルをミュートしていてもノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + + test('閲覧中チャンネルをミュートしていても、同チャンネルのリノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('閲覧中チャンネルをミュートしていても、同チャンネルのリプライが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + await muteChannel(channel.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel.id }); + const bobRenote = await post(bob, { channelId: channel.id, replyId: bobNote.id, text: 'ho' }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + + test('閲覧中チャンネルとは別チャンネルをミュートしているとき、そのチャンネルからのリノートは含まれない', async() => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await createChannel('channel', bob); + const channel2 = await createChannel('channel', bob); + await muteChannel(channel2.id, alice); + + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'ok', channelId: channel2.id }); + const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('channels/timeline', { channelId: channel.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false); + }); + }); // TODO: リノートミュート済みユーザーのテスト // TODO: ページネーションのテスト }); diff --git a/packages/backend/test/jest.setup.unit.cjs b/packages/backend/test/jest.setup.unit.cjs new file mode 100644 index 0000000000..dd879c81c8 --- /dev/null +++ b/packages/backend/test/jest.setup.unit.cjs @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +module.exports = async () => { + // DBはUTC(っぽい)ので、テスト側も合わせておく + process.env.TZ = 'UTC'; + process.env.NODE_ENV = 'test'; +}; diff --git a/packages/backend/test/unit/ChannelFollowingService.ts b/packages/backend/test/unit/ChannelFollowingService.ts new file mode 100644 index 0000000000..2d3196f2f4 --- /dev/null +++ b/packages/backend/test/unit/ChannelFollowingService.ts @@ -0,0 +1,235 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable */ + +import { afterEach, beforeEach, describe, expect } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { IdService } from '@/core/IdService.js'; +import { + type ChannelFollowingsRepository, + ChannelsRepository, + DriveFilesRepository, + MiChannel, + MiChannelFollowing, + MiDriveFile, + MiUser, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ChannelFollowingService } from "@/core/ChannelFollowingService.js"; +import { MiLocalUser } from "@/models/User.js"; + +describe('ChannelFollowingService', () => { + let app: TestingModule; + let service: ChannelFollowingService; + let channelsRepository: ChannelsRepository; + let channelFollowingsRepository: ChannelFollowingsRepository; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let driveFilesRepository: DriveFilesRepository; + let idService: IdService; + + let alice: MiLocalUser; + let bob: MiLocalUser; + let channel1: MiChannel; + let channel2: MiChannel; + let channel3: MiChannel; + let driveFile1: MiDriveFile; + let driveFile2: MiDriveFile; + + async function createUser(data: Partial = {}) { + const user = await usersRepository + .insert({ + id: idService.gen(), + username: 'username', + usernameLower: 'username', + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + }); + + return user; + } + + async function createChannel(data: Partial = {}) { + return await channelsRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => channelsRepository.findOneByOrFail(x.identifiers[0])); + } + + async function createChannelFollowing(data: Partial = {}) { + return await channelFollowingsRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => channelFollowingsRepository.findOneByOrFail(x.identifiers[0])); + } + + async function fetchChannelFollowing() { + return await channelFollowingsRepository.findBy({}); + } + + async function createDriveFile(data: Partial = {}) { + return await driveFilesRepository + .insert({ + id: idService.gen(), + md5: 'md5', + name: 'name', + size: 0, + type: 'type', + storedInternal: false, + url: 'url', + ...data, + }) + .then(x => driveFilesRepository.findOneByOrFail(x.identifiers[0])); + } + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + CoreModule, + ], + providers: [ + GlobalEventService, + IdService, + ChannelFollowingService, + ], + }).compile(); + + app.enableShutdownHooks(); + + service = app.get(ChannelFollowingService); + idService = app.get(IdService); + channelsRepository = app.get(DI.channelsRepository); + channelFollowingsRepository = app.get(DI.channelFollowingsRepository); + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + driveFilesRepository = app.get(DI.driveFilesRepository); + }); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + alice = { ...await createUser({ username: 'alice' }), host: null, uri: null }; + bob = { ...await createUser({ username: 'bob' }), host: null, uri: null }; + driveFile1 = await createDriveFile(); + driveFile2 = await createDriveFile(); + channel1 = await createChannel({ name: 'channel1', userId: alice.id, bannerId: driveFile1.id }); + channel2 = await createChannel({ name: 'channel2', userId: alice.id, bannerId: driveFile2.id }); + channel3 = await createChannel({ name: 'channel3', userId: alice.id, bannerId: driveFile2.id }); + }); + + afterEach(async () => { + await channelFollowingsRepository.deleteAll(); + await channelsRepository.deleteAll(); + await userProfilesRepository.deleteAll(); + await usersRepository.deleteAll(); + }); + + describe('list', () => { + test('default', async () => { + await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id }); + await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id }); + await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id }); + + const followings = await service.list({ requestUserId: alice.id }); + + expect(followings).toHaveLength(2); + expect(followings[0].id).toBe(channel1.id); + expect(followings[0].userId).toBe(alice.id); + expect(followings[0].user).toBeFalsy(); + expect(followings[0].bannerId).toBe(driveFile1.id); + expect(followings[0].banner).toBeFalsy(); + expect(followings[1].id).toBe(channel2.id); + expect(followings[1].userId).toBe(alice.id); + expect(followings[1].user).toBeFalsy(); + expect(followings[1].bannerId).toBe(driveFile2.id); + expect(followings[1].banner).toBeFalsy(); + }); + + test('idOnly', async () => { + await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id }); + await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id }); + await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id }); + + const followings = await service.list({ requestUserId: alice.id }, { idOnly: true }); + + expect(followings).toHaveLength(2); + expect(followings[0].id).toBe(channel1.id); + expect(followings[1].id).toBe(channel2.id); + }); + + test('joinUser', async () => { + await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id }); + await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id }); + await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id }); + + const followings = await service.list({ requestUserId: alice.id }, { joinUser: true }); + + expect(followings).toHaveLength(2); + expect(followings[0].id).toBe(channel1.id); + expect(followings[0].user).toEqual(alice); + expect(followings[0].banner).toBeFalsy(); + expect(followings[1].id).toBe(channel2.id); + expect(followings[1].user).toEqual(alice); + expect(followings[1].banner).toBeFalsy(); + }); + + test('joinBannerFile', async () => { + await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id }); + await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id }); + await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id }); + + const followings = await service.list({ requestUserId: alice.id }, { joinBannerFile: true }); + + expect(followings).toHaveLength(2); + expect(followings[0].id).toBe(channel1.id); + expect(followings[0].user).toBeFalsy(); + expect(followings[0].banner).toEqual(driveFile1); + expect(followings[1].id).toBe(channel2.id); + expect(followings[1].user).toBeFalsy(); + expect(followings[1].banner).toEqual(driveFile2); + }); + }); + + describe('follow', () => { + test('default', async () => { + await service.follow(alice, channel1); + + const followings = await fetchChannelFollowing(); + + expect(followings).toHaveLength(1); + expect(followings[0].followeeId).toBe(channel1.id); + expect(followings[0].followerId).toBe(alice.id); + }); + }); + + describe('unfollow', () => { + test('default', async () => { + await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id }); + + await service.unfollow(alice, channel1); + + const followings = await fetchChannelFollowing(); + + expect(followings).toHaveLength(0); + }); + }); +}); diff --git a/packages/backend/test/unit/ChannelMutingService.ts b/packages/backend/test/unit/ChannelMutingService.ts new file mode 100644 index 0000000000..6916701d1f --- /dev/null +++ b/packages/backend/test/unit/ChannelMutingService.ts @@ -0,0 +1,336 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable */ + +import { afterEach, beforeEach, describe, expect } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { IdService } from '@/core/IdService.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { + ChannelMutingRepository, + ChannelsRepository, + DriveFilesRepository, + MiChannel, + MiChannelMuting, + MiDriveFile, + MiUser, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { setTimeout } from 'node:timers/promises'; + +describe('ChannelMutingService', () => { + let app: TestingModule; + let service: ChannelMutingService; + let channelsRepository: ChannelsRepository; + let channelMutingRepository: ChannelMutingRepository; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let driveFilesRepository: DriveFilesRepository; + let idService: IdService; + + let alice: MiUser; + let bob: MiUser; + let channel1: MiChannel; + let channel2: MiChannel; + let channel3: MiChannel; + let driveFile1: MiDriveFile; + let driveFile2: MiDriveFile; + + async function createUser(data: Partial = {}) { + const user = await usersRepository + .insert({ + id: idService.gen(), + username: 'username', + usernameLower: 'username', + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + }); + + return user; + } + + async function createChannel(data: Partial = {}) { + return await channelsRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => channelsRepository.findOneByOrFail(x.identifiers[0])); + } + + async function createChannelMuting(data: Partial = {}) { + return await channelMutingRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => channelMutingRepository.findOneByOrFail(x.identifiers[0])); + } + + async function fetchChannelMuting() { + return await channelMutingRepository.findBy({}); + } + + async function createDriveFile(data: Partial = {}) { + return await driveFilesRepository + .insert({ + id: idService.gen(), + md5: 'md5', + name: 'name', + size: 0, + type: 'type', + storedInternal: false, + url: 'url', + ...data, + }) + .then(x => driveFilesRepository.findOneByOrFail(x.identifiers[0])); + } + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + CoreModule, + ], + providers: [ + GlobalEventService, + IdService, + ChannelMutingService, + ], + }).compile(); + + app.enableShutdownHooks(); + + service = app.get(ChannelMutingService); + idService = app.get(IdService); + channelsRepository = app.get(DI.channelsRepository); + channelMutingRepository = app.get(DI.channelMutingRepository); + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + driveFilesRepository = app.get(DI.driveFilesRepository); + }); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + alice = await createUser({ username: 'alice' }); + bob = await createUser({ username: 'bob' }); + driveFile1 = await createDriveFile(); + driveFile2 = await createDriveFile(); + channel1 = await createChannel({ name: 'channel1', userId: alice.id, bannerId: driveFile1.id }); + channel2 = await createChannel({ name: 'channel2', userId: alice.id, bannerId: driveFile2.id }); + channel3 = await createChannel({ name: 'channel3', userId: alice.id, bannerId: driveFile2.id }); + }); + + afterEach(async () => { + await channelMutingRepository.deleteAll(); + await channelsRepository.deleteAll(); + await userProfilesRepository.deleteAll(); + await usersRepository.deleteAll(); + }); + + describe('list', () => { + test('default', async () => { + await createChannelMuting({ userId: alice.id, channelId: channel1.id }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id }); + + const mutings = await service.list({ requestUserId: alice.id }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel1.id); + expect(mutings[0].userId).toBe(alice.id); + expect(mutings[0].user).toBeFalsy(); + expect(mutings[0].bannerId).toBe(driveFile1.id); + expect(mutings[0].banner).toBeFalsy(); + expect(mutings[1].id).toBe(channel2.id); + expect(mutings[1].userId).toBe(alice.id); + expect(mutings[1].user).toBeFalsy(); + expect(mutings[1].bannerId).toBe(driveFile2.id); + expect(mutings[1].banner).toBeFalsy(); + }); + + test('withoutExpires', async () => { + const now = new Date(); + const past = new Date(now); + const future = new Date(now); + past.setMinutes(past.getMinutes() - 1); + future.setMinutes(future.getMinutes() + 1); + + await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: null }); + await createChannelMuting({ userId: alice.id, channelId: channel3.id, expiresAt: future }); + + const mutings = await service.list({ requestUserId: alice.id }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel2.id); + expect(mutings[1].id).toBe(channel3.id); + }); + + test('idOnly', async () => { + await createChannelMuting({ userId: alice.id, channelId: channel1.id }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id }); + + const mutings = await service.list({ requestUserId: alice.id }, { idOnly: true }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel1.id); + expect(mutings[1].id).toBe(channel2.id); + }); + + test('withoutExpires-idOnly', async () => { + const now = new Date(); + const past = new Date(now); + const future = new Date(now); + past.setMinutes(past.getMinutes() - 1); + future.setMinutes(future.getMinutes() + 1); + + await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: null }); + await createChannelMuting({ userId: alice.id, channelId: channel3.id, expiresAt: future }); + + const mutings = await service.list({ requestUserId: alice.id }, { idOnly: true }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel2.id); + expect(mutings[1].id).toBe(channel3.id); + }); + + test('joinUser', async () => { + await createChannelMuting({ userId: alice.id, channelId: channel1.id }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id }); + + const mutings = await service.list({ requestUserId: alice.id }, { joinUser: true }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel1.id); + expect(mutings[0].user).toEqual(alice); + expect(mutings[0].banner).toBeFalsy(); + expect(mutings[1].id).toBe(channel2.id); + expect(mutings[1].user).toEqual(alice); + expect(mutings[1].banner).toBeFalsy(); + }); + + test('joinBannerFile', async () => { + await createChannelMuting({ userId: alice.id, channelId: channel1.id }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id }); + + const mutings = await service.list({ requestUserId: alice.id }, { joinBannerFile: true }); + + expect(mutings).toHaveLength(2); + expect(mutings[0].id).toBe(channel1.id); + expect(mutings[0].user).toBeFalsy(); + expect(mutings[0].banner).toEqual(driveFile1); + expect(mutings[1].id).toBe(channel2.id); + expect(mutings[1].user).toBeFalsy(); + expect(mutings[1].banner).toEqual(driveFile2); + }); + }); + + describe('findExpiredMutings', () => { + test('default', async () => { + const now = new Date(); + const future = new Date(now); + const past = new Date(now); + future.setMinutes(now.getMinutes() + 1); + past.setMinutes(now.getMinutes() - 1); + + await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: future }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id, expiresAt: past }); + + const mutings = await service.findExpiredMutings(); + + expect(mutings).toHaveLength(2); + expect(mutings[0].channelId).toBe(channel1.id); + expect(mutings[1].channelId).toBe(channel3.id); + }); + }); + + describe('isMuted', () => { + test('isMuted: true', async () => { + // キャッシュを読むのでServiceの機能を使って登録し、キャッシュを作成する + await service.mute({ requestUserId: alice.id, targetChannelId: channel1.id }); + await service.mute({ requestUserId: alice.id, targetChannelId: channel2.id }); + + await setTimeout(500); + + const result = await service.isMuted({ requestUserId: alice.id, targetChannelId: channel1.id }); + + expect(result).toBe(true); + }); + + test('isMuted: false', async () => { + await service.mute({ requestUserId: alice.id, targetChannelId: channel2.id }); + + await setTimeout(500); + + const result = await service.isMuted({ requestUserId: alice.id, targetChannelId: channel1.id }); + + expect(result).toBe(false); + }); + }); + + describe('mute', () => { + test('default', async () => { + await service.mute({ requestUserId: alice.id, targetChannelId: channel1.id }); + + const muting = await fetchChannelMuting(); + expect(muting).toHaveLength(1); + expect(muting[0].channelId).toBe(channel1.id); + }); + }); + + describe('unmute', () => { + test('default', async () => { + await createChannelMuting({ userId: alice.id, channelId: channel1.id }); + + let muting = await fetchChannelMuting(); + expect(muting).toHaveLength(1); + expect(muting[0].channelId).toBe(channel1.id); + + await service.unmute({ requestUserId: alice.id, targetChannelId: channel1.id }); + + muting = await fetchChannelMuting(); + expect(muting).toHaveLength(0); + }); + }); + + describe('eraseExpiredMutings', () => { + test('default', async () => { + const now = new Date(); + const future = new Date(now); + const past = new Date(now); + future.setMinutes(now.getMinutes() + 1); + past.setMinutes(now.getMinutes() - 1); + + await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past }); + await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: future }); + await createChannelMuting({ userId: bob.id, channelId: channel3.id, expiresAt: past }); + + await service.eraseExpiredMutings(); + + const mutings = await fetchChannelMuting(); + expect(mutings).toHaveLength(1); + expect(mutings[0].channelId).toBe(channel2.id); + }); + }); +}); diff --git a/packages/backend/test/unit/NoteCreateService.ts b/packages/backend/test/unit/NoteCreateService.ts index 23f409420e..f3d3d1da99 100644 --- a/packages/backend/test/unit/NoteCreateService.ts +++ b/packages/backend/test/unit/NoteCreateService.ts @@ -61,6 +61,7 @@ describe('NoteCreateService', () => { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, }; const poll: IPoll = { diff --git a/packages/backend/test/unit/misc/is-renote.ts b/packages/backend/test/unit/misc/is-renote.ts index 74d17abcb6..3c628d8298 100644 --- a/packages/backend/test/unit/misc/is-renote.ts +++ b/packages/backend/test/unit/misc/is-renote.ts @@ -44,6 +44,7 @@ const base: MiNote = { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, }; describe('misc:is-renote', () => { diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 7c19e6798d..dff8be2a57 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -129,24 +129,25 @@ useInterval(() => { }); watch(() => props.channelId, async () => { - channel.value = await misskeyApi('channels/show', { + const _channel = await misskeyApi('channels/show', { channelId: props.channelId, }); - if (channel.value == null) return; // TSを黙らすため - favorited.value = channel.value.isFavorited ?? false; - if (favorited.value || channel.value.isFollowing) { + favorited.value = _channel.isFavorited ?? false; + if (favorited.value || _channel.isFollowing) { tab.value = 'timeline'; } - if ((favorited.value || channel.value.isFollowing) && channel.value.lastNotedAt) { - const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.value.id}`) ?? 0; - const lastNotedAt = Date.parse(channel.value.lastNotedAt); + if ((favorited.value || _channel.isFollowing) && _channel.lastNotedAt) { + const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${_channel.id}`) ?? 0; + const lastNotedAt = Date.parse(_channel.lastNotedAt); if (lastNotedAt > lastReadedAt) { - miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, lastNotedAt); + miLocalStorage.setItemAsJson(`channelLastReadedAt:${_channel.id}`, lastNotedAt); } } + + channel.value = _channel; }, { immediate: true }); function edit() { @@ -190,6 +191,53 @@ async function unfavorite() { }); } +async function mute() { + if (!channel.value) return; + const _channel = channel.value; + + const { canceled, result: period } = await os.select({ + title: i18n.ts.mutePeriod, + items: [{ + value: 'indefinitely', text: i18n.ts.indefinitely, + }, { + value: 'tenMinutes', text: i18n.ts.tenMinutes, + }, { + value: 'oneHour', text: i18n.ts.oneHour, + }, { + value: 'oneDay', text: i18n.ts.oneDay, + }, { + value: 'oneWeek', text: i18n.ts.oneWeek, + }], + default: 'indefinitely', + }); + if (canceled) return; + + const expiresAt = period === 'indefinitely' ? null + : period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10) + : period === 'oneHour' ? Date.now() + (1000 * 60 * 60) + : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) + : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) + : null; + + os.apiWithDialog('channels/mute/create', { + channelId: _channel.id, + expiresAt, + }).then(() => { + _channel.isMuting = true; + }); +} + +async function unmute() { + if (!channel.value) return; + const _channel = channel.value; + + os.apiWithDialog('channels/mute/delete', { + channelId: _channel.id, + }).then(() => { + _channel.isMuting = false; + }); +} + async function search() { if (!channel.value) return; @@ -243,6 +291,24 @@ const headerActions = computed(() => { }); } + if (!channel.value.isMuting) { + headerItems.push({ + icon: 'ti ti-volume', + text: i18n.ts.mute, + handler: async (): Promise => { + await mute(); + }, + }); + } else { + headerItems.push({ + icon: 'ti ti-volume-off', + text: i18n.ts.unmute, + handler: async (): Promise => { + await unmute(); + }, + }); + } + if (($i && $i.id === channel.value.userId) || iAmModerator) { headerItems.push({ icon: 'ti ti-settings', diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 32af619a49..bb490c178b 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -938,6 +938,15 @@ type ChannelsFollowedResponse = operations['channels___followed']['responses'][' // @public (undocumented) type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json']; +// @public (undocumented) +type ChannelsMuteCreateRequest = operations['channels___mute___create']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ChannelsMuteDeleteRequest = operations['channels___mute___delete']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ChannelsMuteListResponse = operations['channels___mute___list']['responses']['200']['content']['application/json']; + // @public (undocumented) type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; @@ -1678,6 +1687,9 @@ declare namespace entities { ChannelsFollowRequest, ChannelsFollowedRequest, ChannelsFollowedResponse, + ChannelsMuteCreateRequest, + ChannelsMuteDeleteRequest, + ChannelsMuteListResponse, ChannelsMyFavoritesResponse, ChannelsOwnedRequest, ChannelsOwnedResponse, diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json index 21ff792f99..7c276e93c7 100644 --- a/packages/misskey-js/generator/package.json +++ b/packages/misskey-js/generator/package.json @@ -15,7 +15,8 @@ "openapi-typescript": "7.9.1", "ts-case-convert": "2.1.0", "tsx": "4.20.6", - "typescript": "5.9.3" + "typescript": "5.9.3", + "eslint": "9.27.0" }, "files": [ "built" diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index c4428efcc2..af3a09a16e 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1391,6 +1391,39 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:channels* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 4b83a9dd9b..c3ef3de4e6 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -182,6 +182,9 @@ import type { ChannelsFollowRequest, ChannelsFollowedRequest, ChannelsFollowedResponse, + ChannelsMuteCreateRequest, + ChannelsMuteDeleteRequest, + ChannelsMuteListResponse, ChannelsMyFavoritesResponse, ChannelsOwnedRequest, ChannelsOwnedResponse, @@ -782,6 +785,9 @@ export type Endpoints = { 'channels/featured': { req: EmptyRequest; res: ChannelsFeaturedResponse }; 'channels/follow': { req: ChannelsFollowRequest; res: EmptyResponse }; 'channels/followed': { req: ChannelsFollowedRequest; res: ChannelsFollowedResponse }; + 'channels/mute/create': { req: ChannelsMuteCreateRequest; res: EmptyResponse }; + 'channels/mute/delete': { req: ChannelsMuteDeleteRequest; res: EmptyResponse }; + 'channels/mute/list': { req: EmptyRequest; res: ChannelsMuteListResponse }; 'channels/my-favorites': { req: EmptyRequest; res: ChannelsMyFavoritesResponse }; 'channels/owned': { req: ChannelsOwnedRequest; res: ChannelsOwnedResponse }; 'channels/search': { req: ChannelsSearchRequest; res: ChannelsSearchResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 4ebe9a5155..0d57b065dc 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -185,6 +185,9 @@ export type ChannelsFeaturedResponse = operations['channels___featured']['respon export type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json']; export type ChannelsFollowedRequest = operations['channels___followed']['requestBody']['content']['application/json']; export type ChannelsFollowedResponse = operations['channels___followed']['responses']['200']['content']['application/json']; +export type ChannelsMuteCreateRequest = operations['channels___mute___create']['requestBody']['content']['application/json']; +export type ChannelsMuteDeleteRequest = operations['channels___mute___delete']['requestBody']['content']['application/json']; +export type ChannelsMuteListResponse = operations['channels___mute___list']['responses']['200']['content']['application/json']; export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; export type ChannelsOwnedRequest = operations['channels___owned']['requestBody']['content']['application/json']; export type ChannelsOwnedResponse = operations['channels___owned']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 3e95651071..4867854b20 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -1140,6 +1140,33 @@ export type paths = { */ post: operations['channels___followed']; }; + '/channels/mute/create': { + /** + * channels/mute/create + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + post: operations['channels___mute___create']; + }; + '/channels/mute/delete': { + /** + * channels/mute/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + post: operations['channels___mute___delete']; + }; + '/channels/mute/list': { + /** + * channels/mute/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:channels* + */ + post: operations['channels___mute___list']; + }; '/channels/my-favorites': { /** * channels/my-favorites @@ -4929,6 +4956,7 @@ export type components = { allowRenoteToExternal: boolean; isFollowing?: boolean; isFavorited?: boolean; + isMuting?: boolean; pinnedNotes?: components['schemas']['Note'][]; }; QueueCount: { @@ -14790,6 +14818,192 @@ export interface operations { }; }; }; + channels___mute___create: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + channelId: string; + /** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */ + expiresAt?: number | null; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + headers: { + [name: string]: unknown; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + channels___mute___delete: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + channelId: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + headers: { + [name: string]: unknown; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + channels___mute___list: { + responses: { + /** @description OK (with results) */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Channel'][]; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; 'channels___my-favorites': { responses: { /** @description OK (with results) */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e45b17726..b0b82e0251 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1478,10 +1478,13 @@ importers: version: 24.9.1 '@typescript-eslint/eslint-plugin': specifier: 8.46.1 - version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) + version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3) '@typescript-eslint/parser': specifier: 8.46.1 - version: 8.46.1(eslint@9.37.0)(typescript@5.9.3) + version: 8.46.1(eslint@9.27.0)(typescript@5.9.3) + eslint: + specifier: 9.27.0 + version: 9.27.0 openapi-types: specifier: 12.1.3 version: 12.1.3 @@ -2274,14 +2277,30 @@ packages: resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.20.1': + resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.21.0': resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.2.3': + resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.4.0': resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2290,6 +2309,10 @@ packages: resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.27.0': + resolution: {integrity: sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.37.0': resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2298,6 +2321,10 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.4.0': resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6420,6 +6447,16 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@9.27.0: + resolution: {integrity: sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + eslint@9.37.0: resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -12394,6 +12431,11 @@ snapshots: '@esbuild/win32-x64@0.25.10': optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.27.0)': + dependencies: + eslint: 9.27.0 + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0)': dependencies: eslint: 9.37.0 @@ -12403,6 +12445,14 @@ snapshots: '@eslint/compat@1.1.1': {} + '@eslint/config-array@0.20.1': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3(supports-color@10.2.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 @@ -12411,10 +12461,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-helpers@0.2.3': {} + '@eslint/config-helpers@0.4.0': dependencies: '@eslint/core': 0.16.0 + '@eslint/core@0.14.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': dependencies: '@types/json-schema': 7.0.15 @@ -12433,10 +12493,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/js@9.27.0': {} + '@eslint/js@9.37.0': {} '@eslint/object-schema@2.1.6': {} + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + '@eslint/plugin-kit@0.4.0': dependencies: '@eslint/core': 0.16.0 @@ -15251,6 +15318,23 @@ snapshots: '@types/node': 24.9.1 optional: true + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.46.1(eslint@9.27.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/type-utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + eslint: 9.27.0 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -15268,6 +15352,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + debug: 4.4.3(supports-color@10.2.0) + eslint: 9.27.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.46.1 @@ -15298,6 +15394,18 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/type-utils@8.46.1(eslint@9.27.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3) + debug: 4.4.3(supports-color@10.2.0) + eslint: 9.27.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.46.1 @@ -15328,6 +15436,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.46.1(eslint@9.27.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.27.0) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + eslint: 9.27.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0) @@ -17322,6 +17441,46 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint@9.27.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.27.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.1 + '@eslint/config-helpers': 0.2.3 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.27.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@10.2.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + eslint@9.37.0: dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0) From 37fe7a96343c248cc5fc8c8a2ad3d2fa9aa9eca5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Nov 2025 23:43:26 +0000 Subject: [PATCH 31/79] Bump version to 2025.11.0-alpha.2 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 009f478475..b9980f9c26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.11.0-alpha.1", + "version": "2025.11.0-alpha.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index c23ea7fffa..e1689a03a3 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.11.0-alpha.1", + "version": "2025.11.0-alpha.2", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From e8c78e12d5674df9099a4f7b83102ba5d82c9ee8 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:03:23 +0900 Subject: [PATCH 32/79] Update package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b9980f9c26..944e19469a 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,8 @@ "@aiscript-dev/aiscript-languageserver": "-" }, "ignoredBuiltDependencies": [ - "@sentry-internal/node-cpu-profiler" + "@sentry-internal/node-cpu-profiler", + "exifreader" ] } } From e312283ea016630521c972cd42439655759a48a7 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:03:33 +0900 Subject: [PATCH 33/79] =?UTF-8?q?enhance(frontend):=20=E6=8A=95=E7=A8=BF?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AE=E3=83=92=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=20(#16712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * Update MkSpot.vue * Update MkPostForm.vue * wip * wip * Update CHANGELOG.md --- CHANGELOG.md | 1 + locales/index.d.ts | 54 ++++++ locales/ja-JP.yml | 14 ++ .../frontend/src/components/MkPostForm.vue | 56 +++++- packages/frontend/src/components/MkSpot.vue | 161 ++++++++++++++++++ packages/frontend/src/tips.ts | 1 + packages/frontend/src/utility/tour.ts | 49 ++++++ 7 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 packages/frontend/src/components/MkSpot.vue create mode 100644 packages/frontend/src/utility/tour.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d32ff1ec42..1ea6e2efd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Enhance: プロフィールへのリンクをユーザーポップアップのアバターに追加 - Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加 - Enhance: プッシュ通知を行うための権限確認をより確実に行うように +- Enhance: 投稿フォームのチュートリアルを追加 - Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 - Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように - Fix: ページのタイトルが長いとき、はみ出る問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index e4ebfedd3d..0d0c1cfc53 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10030,6 +10030,60 @@ export interface Locale extends ILocale { * チャンネルに投稿... */ "channelPlaceholder": string; + /** + * フォームの説明を表示 + */ + "showHowToUse": string; + "_howToUse": { + /** + * 本文 + */ + "content_title": string; + /** + * 投稿する内容を入力します。 + */ + "content_description": string; + /** + * ツールバー + */ + "toolbar_title": string; + /** + * ファイルやアンケートの添付、注釈やハッシュタグの設定、絵文字やメンションの挿入などが行えます。 + */ + "toolbar_description": string; + /** + * アカウントメニュー + */ + "account_title": string; + /** + * 投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。 + */ + "account_description": string; + /** + * 公開範囲 + */ + "visibility_title": string; + /** + * ノートを公開する範囲の設定が行えます。 + */ + "visibility_description": string; + /** + * メニュー + */ + "menu_title": string; + /** + * 下書きへの保存、投稿の予約、リアクションの設定など、その他のアクションが行えます。 + */ + "menu_description": string; + /** + * 投稿ボタン + */ + "submit_title": string; + /** + * ノートを投稿します。Ctrl + Enter / Cmd + Enter でも投稿できます。 + */ + "submit_description": string; + }; "_placeholders": { /** * いまどうしてる? diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d0485af208..e40c083cff 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2641,6 +2641,20 @@ _postForm: replyPlaceholder: "このノートに返信..." quotePlaceholder: "このノートを引用..." channelPlaceholder: "チャンネルに投稿..." + showHowToUse: "フォームの説明を表示" + _howToUse: + content_title: "本文" + content_description: "投稿する内容を入力します。" + toolbar_title: "ツールバー" + toolbar_description: "ファイルやアンケートの添付、注釈やハッシュタグの設定、絵文字やメンションの挿入などが行えます。" + account_title: "アカウントメニュー" + account_description: "投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。" + visibility_title: "公開範囲" + visibility_description: "ノートを公開する範囲の設定が行えます。" + menu_title: "メニュー" + menu_description: "下書きへの保存、投稿の予約、リアクションの設定など、その他のアクションが行えます。" + submit_title: "投稿ボタン" + submit_description: "ノートを投稿します。Ctrl + Enter / Cmd + Enter でも投稿できます。" _placeholders: a: "いまどうしてる?" b: "何かありましたか?" diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 664ff2d469..140b4aa887 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+