diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index a028e2685e..8b11c8413c 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -215,20 +215,9 @@ proxyBypassHosts: # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: true) -proxyRemoteFiles: true - -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true - allowedPrivateNetworks: [ '127.0.0.1/32' ] -# Disable automatic redirect for ActivityPub object lookup. (default: false) -# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation. -# However it will make it impossible for other instances to lookup third-party user and notes through your URL. -#disallowExternalApRedirect: true - # Upload or download file size limits (bytes) #maxFileSize: 262144000 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 4be1352bd7..dc354324dc 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -227,12 +227,6 @@ proxyBypassHosts: # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: true) -proxyRemoteFiles: true - -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true - # For security reasons, uploading attachments from the intranet is prohibited, # but exceptions can be made from the following settings. Default value is "undefined". # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). @@ -240,11 +234,6 @@ signToActivityPubGet: true # '127.0.0.1/32' #] -# Disable automatic redirect for ActivityPub object lookup. (default: false) -# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation. -# However it will make it impossible for other instances to lookup third-party user and notes through your URL. -#disallowExternalApRedirect: true - # Upload or download file size limits (bytes) #maxFileSize: 262144000 diff --git a/.config/example.yml b/.config/example.yml index d4584215c9..c7884a3687 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -105,6 +105,54 @@ port: 3000 # socket: /path/to/misskey.sock # chmodSocket: '777' +# Proxy trust settings +# +# Specifies the IP addresses that Misskey will use as trusted +# reverse proxies (e.g., nginx, Cloudflare). This affects how +# Misskey determines the source IP for each request and is used +# for important rate limiting and security features. If the value +# is not set correctly, Misskey may use the IP address of the +# reverse proxy instead of the actual source IP, which may lead to +# unintended rate limiting or security vulnerabilities. +# By default, the loopback network and private network address +# ranges shown below are trusted. +# If you are using a single reverse proxy and it is on the same +# machine or the same private network as Misskey, it is unlikely you +# need to change this setting, and the default setting is fine. +# Also, if you are using multiple reverse proxy servers and they are +# all on the same private network as Misskey, the default setting +# is fine. +# However, if you are using a reverse proxy server that accesses +# Misskey web servers and streaming servers via public IP addresses +# (for example, Cloudflare), you must set this variable. +# When changing this setting, you can use one of the following values: +# +# - true: Trust all proxies +# - false: Do not trust any proxies +# - IP address, IP address range, or array of them: Trust hops that +# match the specified criteria. +# - Integer: Trust the nth hop from the front-facing proxy server as +# the client. +# For more information on how to configure this setting, please refer +# to the Fastify documentation: +# https://fastify.dev/docs/latest/Reference/Server/#trustproxy +# +# Note that if this variable is set, it overrides the default range, +# so if you have both an external reverse proxy and a proxy on the +# local host, you must include both IPs (or IP ranges). +# +#trustProxy: +# - '10.0.0.0/8' +# - '172.16.0.0/12' +# - '192.168.0.0/16' +# - '127.0.0.1/32' +# - '::1/128' +# - 'fc00::/7' +# # Example: If you are using some external reverse proxies like CDNs, +# # you may need to add the CDN IP ranges here. +# # If you're using Cloudflare, you can find IP Ranges at: +# # https://www.cloudflare.com/ips/ + # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── @@ -273,6 +321,10 @@ id: 'aidx' # Whether disable HSTS #disableHsts: true +# Enable internal IP-based rate limiting (default: true) +# To configure them in reverse proxy instead, set this to false. +#enableIpRateLimit: true + # Number of worker processes #clusterLimit: 1 @@ -319,19 +371,12 @@ proxyBypassHosts: # * Perform image compression (on a different server resource than the main process) #mediaProxy: https://example.com/proxy -# Proxy remote files (default: true) -# Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains. -proxyRemoteFiles: true - # Movie Thumbnail Generation URL # There is no reference implementation. # For example, Misskey will point to the following URL: # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 #videoThumbnailGenerator: https://example.com -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true - # For security reasons, uploading attachments from the intranet is prohibited, # but exceptions can be made from the following settings. Default value is "undefined". # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). @@ -339,11 +384,6 @@ signToActivityPubGet: true # '127.0.0.1/32' #] -# Disable automatic redirect for ActivityPub object lookup. (default: false) -# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation. -# However it will make it impossible for other instances to lookup third-party user and notes through your URL. -#disallowExternalApRedirect: true - # Upload or download file size limits (bytes) #maxFileSize: 262144000 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b6ebcf6ad3..d208ad6ecf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM mcr.microsoft.com/devcontainers/javascript-node:0-18 +FROM mcr.microsoft.com/devcontainers/javascript-node:4.0.3-24-trixie diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml index d02d2a8f4a..501f78c814 100644 --- a/.devcontainer/compose.yml +++ b/.devcontainer/compose.yml @@ -28,7 +28,7 @@ services: db: restart: unless-stopped - image: postgres:15-alpine + image: postgres:18-alpine networks: - internal_network environment: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c506c36f6b..514abdfb20 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,10 +5,10 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "22.11.0" + "version": "22.15.0" }, "ghcr.io/devcontainers-extra/features/pnpm:2": { - "version": "10.6.1" + "version": "10.10.0" } }, "forwardPorts": [3000], diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 6d904e87b9..fb0d25c214 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -202,12 +202,6 @@ proxyBypassHosts: # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: true) -proxyRemoteFiles: true - -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true - allowedPrivateNetworks: [ '127.0.0.1/32' ] diff --git a/.dockerignore b/.dockerignore index f204349160..39cbe2726f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ Dockerfile build/ built/ +src-js/ db/ .devcontainer/compose.yml node_modules/ diff --git a/.editorconfig b/.editorconfig index def7baa1a8..ccf388f06e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,7 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_style = space + +[packages/backend/migration/*.js] +indent_style = space +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml index 077855b5bf..00da7e9a2a 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug-report.yml @@ -54,7 +54,7 @@ body: * Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4 * Browser: Chrome 113.0.5672.126 * Server URL: misskey.example.com - * Misskey: 2025.x.x + * Misskey: 2026.x.x value: | * Model and OS of the device(s): * Browser: @@ -74,9 +74,9 @@ body: Examples: * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment - * Misskey: 2025.x.x + * Misskey: 2026.x.x * Node: 20.x.x - * PostgreSQL: 15.x.x + * PostgreSQL: 18.x.x * Redis: 7.x.x * OS and Architecture: Ubuntu 24.04.2 LTS aarch64 value: | diff --git a/.github/min.node-version b/.github/min.node-version new file mode 100644 index 0000000000..b8ffd70759 --- /dev/null +++ b/.github/min.node-version @@ -0,0 +1 @@ +22.15.0 diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml index 3c807e8b9e..513bfb1ac0 100644 --- a/.github/misskey/test.yml +++ b/.github/misskey/test.yml @@ -15,3 +15,5 @@ redis: host: 127.0.0.1 port: 56312 id: aidx + +proxyRemoteFiles: true diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 7d085821b7..49ca3058f3 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -16,13 +16,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Setup Node.js - uses: actions/setup-node@v4.3.0 + uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 2e94f433b7..d17999a271 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -12,9 +12,9 @@ jobs: steps: - name: Checkout head - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Setup Node.js - uses: actions/setup-node@v4.3.0 + uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml index 090dc70bd5..8a81e85521 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 with: submodules: true persist-credentials: false @@ -29,7 +29,7 @@ jobs: - name: setup node id: setup-node - uses: actions/setup-node@v4.3.0 + uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: pnpm @@ -53,7 +53,7 @@ jobs: # packages/misskey-js/generator/built/autogen - name: Upload Generated - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: generated-misskey-js path: packages/misskey-js/generator/built/autogen @@ -66,14 +66,14 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 with: submodules: true persist-credentials: false ref: refs/pull/${{ github.event.pull_request.number }}/merge - name: Upload From Merged - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: actual-misskey-js path: packages/misskey-js/src/autogen @@ -86,13 +86,13 @@ jobs: pull-requests: write steps: - name: download generated-misskey-js - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: generated-misskey-js path: misskey-js-generated - name: download actual-misskey-js - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: actual-misskey-js path: misskey-js-actual @@ -113,9 +113,9 @@ jobs: - name: send message if: steps.check-changes.outputs.changes == 'true' - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: - comment_tag: check-misskey-js-autogen + comment-tag: check-misskey-js-autogen message: |- Thank you for sending us a great Pull Request! 👍 Please regenerate misskey-js type definitions! 🙏 @@ -127,9 +127,9 @@ jobs: - name: send message if: steps.check-changes.outputs.changes == 'false' - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: - comment_tag: check-misskey-js-autogen + comment-tag: check-misskey-js-autogen mode: delete message: "Thank you!" create_if_not_exists: false diff --git a/.github/workflows/check-misskey-js-version.yml b/.github/workflows/check-misskey-js-version.yml index 2b15cbee53..ad07d47b65 100644 --- a/.github/workflows/check-misskey-js-version.yml +++ b/.github/workflows/check-misskey-js-version.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Check version run: | if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml index bc6be308d1..fe71473ea3 100644 --- a/.github/workflows/check-spdx-license-id.yml +++ b/.github/workflows/check-spdx-license-id.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Check run: | counter=0 @@ -50,6 +50,7 @@ jobs: "packages/backend/test" "packages/frontend-shared/@types" "packages/frontend-shared/js" + "packages/frontend-builder" "packages/frontend/.storybook" "packages/frontend/@types" "packages/frontend/lib" @@ -58,6 +59,7 @@ jobs: "packages/frontend/test" "packages/frontend-embed/@types" "packages/frontend-embed/src" + "packages/icons-subsetter/src" "packages/misskey-bubble-game/src" "packages/misskey-reversi/src" "packages/sw/src" diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml index eaf922d4bc..40016d39c5 100644 --- a/.github/workflows/check_copyright_year.yml +++ b/.github/workflows/check_copyright_year.yml @@ -10,7 +10,7 @@ jobs: check_copyright_year: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 - run: | if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then echo "Please change copyright year!" diff --git a/.github/workflows/deploy-test-environment.yml b/.github/workflows/deploy-test-environment.yml index 46baf7421b..32c7c6b6ea 100644 --- a/.github/workflows/deploy-test-environment.yml +++ b/.github/workflows/deploy-test-environment.yml @@ -28,7 +28,7 @@ jobs: wait_time: ${{ steps.get-wait-time.outputs.wait_time }} steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Check allowed users id: check-allowed-users diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index 56dedf273d..8a97959907 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -27,7 +27,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub @@ -53,7 +53,7 @@ jobs: digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -66,7 +66,7 @@ jobs: - build steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: /tmp/digests pattern: digests-* diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eb98273ba0..37f6aca588 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,7 +32,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta @@ -64,7 +64,7 @@ jobs: digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -77,7 +77,7 @@ jobs: - build steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: /tmp/digests pattern: digests-* diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index 3054607913..ec7073c9fd 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -11,22 +11,43 @@ on: jobs: dockle: runs-on: ubuntu-latest + env: DOCKER_CONTENT_TRUST: 1 - DOCKLE_VERSION: 0.4.14 + DOCKLE_VERSION: 0.4.15 + steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 + - name: Download and install dockle v${{ env.DOCKLE_VERSION }} run: | + set -eux curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb" sudo dpkg -i dockle.deb - - run: | - cp .config/docker_example.env .config/docker.env - cp ./compose_example.yml ./compose.yml - - run: | - docker compose up -d web - docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest - - run: | - cmd="dockle --exit-code 1 misskey-web:latest ${image_name}" - echo "> ${cmd}" - eval "${cmd}" + + - name: Build web image (docker build) + run: | + set -eux + docker build -t "misskey-web:ci" . + docker image ls + + - name: Mount tmpfs for Dockle tar + env: + TMPFS_SIZE: 8G + run: | + set -eux + sudo mkdir -p /mnt/dockle-tmp + sudo mount -t tmpfs -o size=${{ env.TMPFS_SIZE }} tmpfs /mnt/dockle-tmp + free -h + df -h + + - name: Save image tar into tmpfs + run: | + set -eux + docker save misskey-web:ci -o /mnt/dockle-tmp/misskey-web.tar + ls -lh /mnt/dockle-tmp/misskey-web.tar + + - name: Run Dockle Scan (tar input) + run: | + set -eux + dockle --exit-code 1 --input /mnt/dockle-tmp/misskey-web.tar diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index c5a4f77336..f8a0c4aaa4 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -17,7 +17,6 @@ jobs: strategy: matrix: - node-version: [22.11.0] api-json-name: [api-base.json, api-head.json] include: - api-json-name: api-base.json @@ -26,16 +25,16 @@ jobs: ref: refs/pull/${{ github.event.number }}/merge steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: ref: ${{ matrix.ref }} submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml @@ -49,7 +48,7 @@ jobs: - name: Copy API.json run: cp packages/backend/built/api.json ${{ matrix.api-json-name }} - name: Upload Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: api-artifact-${{ matrix.api-json-name }} path: ${{ matrix.api-json-name }} @@ -62,7 +61,7 @@ jobs: PR_NUMBER: ${{ github.event.number }} run: | echo "$PR_NUMBER" > ./pr_number - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: api-artifact-pr-number path: pr_number diff --git a/.github/workflows/get-backend-memory.yml b/.github/workflows/get-backend-memory.yml new file mode 100644 index 0000000000..99f89631bb --- /dev/null +++ b/.github/workflows/get-backend-memory.yml @@ -0,0 +1,87 @@ +# this name is used in report-backend-memory.yml so be careful when change name +name: Get backend memory usage + +on: + pull_request: + branches: + - master + - develop + paths: + - packages/backend/** + - packages/misskey-js/** + - .github/workflows/get-backend-memory.yml + +jobs: + get-memory-usage: + runs-on: ubuntu-latest + permissions: + contents: read + + strategy: + matrix: + memory-json-name: [memory-base.json, memory-head.json] + include: + - memory-json-name: memory-base.json + ref: ${{ github.base_ref }} + - memory-json-name: memory-head.json + ref: refs/pull/${{ github.event.number }}/merge + + services: + postgres: + image: postgres:18 + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + redis: + image: redis:7 + ports: + - 56312:6379 + + steps: + - uses: actions/checkout@v6.0.1 + with: + ref: ${{ matrix.ref }} + submodules: true + - name: Setup pnpm + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 + with: + node-version-file: '.node-version' + cache: 'pnpm' + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .github/misskey/test.yml .config/default.yml + - name: Compile Configure + run: pnpm compile-config + - name: Build + run: pnpm build + - name: Run migrations + run: pnpm --filter backend migrate + - name: Measure memory usage + run: | + # Start the server and measure memory usage + node packages/backend/scripts/measure-memory.mjs > ${{ matrix.memory-json-name }} + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: memory-artifact-${{ matrix.memory-json-name }} + path: ${{ matrix.memory-json-name }} + + save-pr-number: + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Save PR number + env: + PR_NUMBER: ${{ github.event.number }} + run: | + echo "$PR_NUMBER" > ./pr_number + - uses: actions/upload-artifact@v6 + with: + name: memory-artifact-pr-number + path: pr_number diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 88e2aceaed..5787572dd5 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,6 +11,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 83f83a67c6..1779fafb89 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,9 @@ on: - packages/backend/** - packages/frontend/** - packages/frontend-shared/** + - packages/frontend-builder/** - packages/frontend-embed/** + - packages/icons-subsetter/** - packages/sw/** - packages/misskey-js/** - packages/misskey-bubble-game/** @@ -22,7 +24,9 @@ on: - packages/backend/** - packages/frontend/** - packages/frontend-shared/** + - packages/frontend-builder/** - packages/frontend-embed/** + - packages/icons-subsetter/** - packages/sw/** - packages/misskey-js/** - packages/misskey-bubble-game/** @@ -34,13 +38,13 @@ jobs: pnpm_install: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: fetch-depth: 0 submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -56,7 +60,9 @@ jobs: - backend - frontend - frontend-shared + - frontend-builder - frontend-embed + - icons-subsetter - sw - misskey-js - misskey-bubble-game @@ -66,19 +72,19 @@ jobs: eslint-cache-version: v1 eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }} steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: fetch-depth: 0 submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.3.0 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} @@ -93,22 +99,20 @@ jobs: matrix: workspace: - backend + - frontend - sw - misskey-js steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: fetch-depth: 0 submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - - run: pnpm --filter misskey-js run build - if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }} - - run: pnpm --filter misskey-mahjong --filter misskey-reversi run build - if: ${{ matrix.workspace == 'backend' }} + - run: pnpm --filter "${{ matrix.workspace }}^..." run build - run: pnpm --filter ${{ matrix.workspace }} run typecheck diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index cee4c27ceb..15cc9153f6 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -3,10 +3,12 @@ name: Lint on: push: paths: + - packages/i18n/** - locales/** - .github/workflows/locale.yml pull_request: paths: + - packages/i18n/** - locales/** - .github/workflows/locale.yml jobs: @@ -14,15 +16,18 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 0 - submodules: true - - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - uses: actions/setup-node@v4.3.0 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - run: pnpm i --frozen-lockfile - - run: cd locales && node verify.js + - uses: actions/checkout@v6.0.1 + with: + fetch-depth: 0 + submodules: true + - name: Setup pnpm + uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6.1.0 + with: + node-version-file: ".node-version" + cache: "pnpm" + - run: pnpm i --frozen-lockfile + - run: pnpm --filter i18n build + - name: Verify Locales + working-directory: ./packages/i18n + run: pnpm run verify diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 9d15e0fcf1..c9a47385a0 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -15,21 +15,18 @@ jobs: contents: read id-token: write - strategy: - matrix: - node-version: [22.11.0] - steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' + # see https://docs.github.com/actions/use-cases-and-examples/publishing-packages/publishing-nodejs-packages#publishing-packages-to-the-npm-registry registry-url: 'https://registry.npmjs.org' - name: Publish package run: | diff --git a/.github/workflows/release-edit-with-push.yml b/.github/workflows/release-edit-with-push.yml index 57657a4ba7..bc16dbcef2 100644 --- a/.github/workflows/release-edit-with-push.yml +++ b/.github/workflows/release-edit-with-push.yml @@ -19,7 +19,7 @@ jobs: edit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 # headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得 - name: Get PR run: | diff --git a/.github/workflows/release-with-dispatch.yml b/.github/workflows/release-with-dispatch.yml index d750001b71..f318200584 100644 --- a/.github/workflows/release-with-dispatch.yml +++ b/.github/workflows/release-with-dispatch.yml @@ -36,7 +36,7 @@ jobs: outputs: pr_number: ${{ steps.get_pr.outputs.pr_number }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 # headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得 - name: Get PRs run: | diff --git a/.github/workflows/report-api-diff.yml b/.github/workflows/report-api-diff.yml index 1170f898ce..59b92d022e 100644 --- a/.github/workflows/report-api-diff.yml +++ b/.github/workflows/report-api-diff.yml @@ -16,7 +16,7 @@ jobs: # api-artifact steps: - name: Download artifact - uses: actions/github-script@v7.0.1 + uses: actions/github-script@v8.0.0 with: script: | const fs = require('fs'); @@ -60,7 +60,7 @@ jobs: - name: Echo full diff run: cat ./api-full.json.diff - name: Upload full diff to Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: api-artifact path: | @@ -73,9 +73,9 @@ jobs: HEADER="このPRによるapi.jsonの差分" FOOTER="[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" DIFF_BYTES="$(stat ./api.json.diff -c '%s' | tr -d '\n')" - + echo "$HEADER" > ./output.md - + if (( "$DIFF_BYTES" <= 1 )); then echo '差分はありません。' >> ./output.md else @@ -87,18 +87,18 @@ jobs: echo '```' >> ./output.md echo '' >> .output.md fi - + echo "$FOOTER" >> ./output.md - - uses: thollander/actions-comment-pull-request@v2 + - uses: thollander/actions-comment-pull-request@v3 with: - pr_number: ${{ steps.load-pr-num.outputs.pr-number }} - comment_tag: show_diff - filePath: ./output.md + pr-number: ${{ steps.load-pr-num.outputs.pr-number }} + comment-tag: show_diff + file-path: ./output.md - name: Tell error to PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 if: failure() && steps.load-pr-num.outputs.pr-number with: - pr_number: ${{ steps.load-pr-num.outputs.pr-number }} - comment_tag: show_diff_error + pr-number: ${{ steps.load-pr-num.outputs.pr-number }} + comment-tag: show_diff_error message: | api.jsonの差分作成中にエラーが発生しました。詳細は[Workflowのログ](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})を確認してください。 diff --git a/.github/workflows/report-backend-memory.yml b/.github/workflows/report-backend-memory.yml new file mode 100644 index 0000000000..c339ca49b4 --- /dev/null +++ b/.github/workflows/report-backend-memory.yml @@ -0,0 +1,122 @@ +name: Report backend memory + +on: + workflow_run: + types: [completed] + workflows: + - Get backend memory usage # get-backend-memory.yml + +jobs: + compare-memory: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + pull-requests: write + + steps: + - name: Download artifact + uses: actions/github-script@v8.0.0 + with: + script: | + const fs = require('fs'); + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name.startsWith("memory-artifact-") || artifact.name == "memory-artifact" + }); + await Promise.all(matchArtifacts.map(async (artifact) => { + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data)); + })); + - name: Extract all artifacts + run: | + find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';' + ls -la artifacts/ + - name: Load PR Number + id: load-pr-num + run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT" + + - name: Output base + run: cat ./artifacts/memory-base.json + - name: Output head + run: cat ./artifacts/memory-head.json + - name: Compare memory usage + id: compare + run: | + BASE_MEMORY=$(cat ./artifacts/memory-base.json) + HEAD_MEMORY=$(cat ./artifacts/memory-head.json) + + BASE_RSS=$(echo "$BASE_MEMORY" | jq -r '.memory.rss // 0') + HEAD_RSS=$(echo "$HEAD_MEMORY" | jq -r '.memory.rss // 0') + + # Calculate difference + if [ "$BASE_RSS" -gt 0 ] && [ "$HEAD_RSS" -gt 0 ]; then + DIFF=$((HEAD_RSS - BASE_RSS)) + DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE_RSS" | bc) + + # Convert to MB for readability + BASE_MB=$(echo "scale=2; $BASE_RSS / 1048576" | bc) + HEAD_MB=$(echo "scale=2; $HEAD_RSS / 1048576" | bc) + DIFF_MB=$(echo "scale=2; $DIFF / 1048576" | bc) + + echo "base_mb=$BASE_MB" >> "$GITHUB_OUTPUT" + echo "head_mb=$HEAD_MB" >> "$GITHUB_OUTPUT" + echo "diff_mb=$DIFF_MB" >> "$GITHUB_OUTPUT" + echo "diff_percent=$DIFF_PERCENT" >> "$GITHUB_OUTPUT" + echo "has_data=true" >> "$GITHUB_OUTPUT" + + # Determine if this is a significant change (more than 5% increase) + if [ "$(echo "$DIFF_PERCENT > 5" | bc)" -eq 1 ]; then + echo "significant_increase=true" >> "$GITHUB_OUTPUT" + else + echo "significant_increase=false" >> "$GITHUB_OUTPUT" + fi + else + echo "has_data=false" >> "$GITHUB_OUTPUT" + fi + - id: build-comment + name: Build memory comment + run: | + HEADER="## Backend Memory Usage Comparison" + FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" + + echo "$HEADER" > ./output.md + echo >> ./output.md + + if [ "${{ steps.compare.outputs.has_data }}" == "true" ]; then + echo "| Metric | base | head | Diff |" >> ./output.md + echo "|--------|------|------|------|" >> ./output.md + echo "| RSS | ${{ steps.compare.outputs.base_mb }} MB | ${{ steps.compare.outputs.head_mb }} MB | ${{ steps.compare.outputs.diff_mb }} MB (${{ steps.compare.outputs.diff_percent }}%) |" >> ./output.md + echo >> ./output.md + + if [ "${{ steps.compare.outputs.significant_increase }}" == "true" ]; then + echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md + echo >> ./output.md + fi + else + echo "Could not retrieve memory usage data." >> ./output.md + echo >> ./output.md + fi + + echo "$FOOTER" >> ./output.md + - uses: thollander/actions-comment-pull-request@v3 + with: + pr-number: ${{ steps.load-pr-num.outputs.pr-number }} + comment-tag: show_memory_diff + file-path: ./output.md + - name: Tell error to PR + uses: thollander/actions-comment-pull-request@v3 + if: failure() && steps.load-pr-num.outputs.pr-number + with: + pr-number: ${{ steps.load-pr-num.outputs.pr-number }} + comment-tag: show_memory_diff_error + message: | + An error occurred while comparing backend memory usage. See [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. diff --git a/.github/workflows/request-release-review.yml b/.github/workflows/request-release-review.yml new file mode 100644 index 0000000000..9b6768149b --- /dev/null +++ b/.github/workflows/request-release-review.yml @@ -0,0 +1,51 @@ +name: Request release review + +on: + issue_comment: + types: [created] + +jobs: + reply: + if: github.event.comment.body == '/request-release-review' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Reply + uses: actions/github-script@v8 + with: + script: | + const body = `To dev team (@misskey-dev/dev): + + リリースが提案されています :rocket: + + GOの場合はapprove、NO GOの場合はその旨コメントをお願いいたします。 + + 判断にあたって考慮すべき観点は、 + + - やり残したことはないか? + - CHANGELOGは過不足ないか? + - バージョンに問題はないか?(月跨いでいるのに更新忘れているなど) + - 再考すべき仕様・実装はないか? + - ベータ版を検証したサーバーから不具合の報告等は上がってないか? + - (セキュリティの修正や重要なバグ修正などのため)リリースを急いだ方が良いか?そうではないか? + - Actionsが落ちていないか? + + などが挙げられます。 + + ご協力ありがとうございます :sparkles: + ` + + const issue_number = context.payload.issue ? context.payload.issue.number : (context.payload.pull_request && context.payload.pull_request.number) + if (!issue_number) { + console.log('No issue or PR number found in payload; skipping') + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + body, + }) + } diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 36ea2eea5f..975cc3845c 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -15,23 +15,19 @@ on: jobs: build: # Chromatic is not likely to be available for fork repositories, so we disable for fork repositories. - # Neither Dependabot nor Renovate will change the actual behavior for components. - if: >- - github.repository == 'misskey-dev/misskey' && - startsWith(github.head_ref, 'refs/heads/dependabot/') != true && - startsWith(github.head_ref, 'refs/heads/renovate/') != true + if: github.repository == 'misskey-dev/misskey' runs-on: ubuntu-latest env: NODE_OPTIONS: "--max_old_space_size=7168" steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 if: github.event_name != 'pull_request_target' with: fetch-depth: 0 submodules: true - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 if: github.event_name == 'pull_request_target' with: fetch-depth: 0 @@ -39,14 +35,11 @@ jobs: ref: "refs/pull/${{ github.event.number }}/merge" - name: Checkout actual HEAD if: github.event_name == 'pull_request_target' - id: rev - run: | - echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT - git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3) + run: git checkout "$(git rev-list --parents -n1 HEAD | cut -d" " -f3)" - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js 20.x - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -85,24 +78,19 @@ jobs: if: github.event_name == 'pull_request_target' id: chromatic_pull_request run: | - DIFF="${{ steps.rev.outputs.base }} HEAD" - if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then - DIFF="HEAD" - fi - CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))" + CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff --name-only origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | xargs))" if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then echo "skip=true" >> $GITHUB_OUTPUT fi - BRANCH="${{ github.event.pull_request.head.user.login }}:$HEAD_REF" - if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then - BRANCH="$HEAD_REF" + BRANCH="${{ github.event.pull_request.head.user.login }}:$GITHUB_HEAD_REF" + if [ "$BRANCH" = "misskey-dev:$GITHUB_HEAD_REF" ]; then + BRANCH="$GITHUB_HEAD_REF" fi pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER") env: - HEAD_REF: ${{ github.event.pull_request.head.ref }} CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Notify that Chromatic detects changes - uses: actions/github-script@v7.0.1 + uses: actions/github-script@v8.0.0 if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false' with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -114,7 +102,7 @@ jobs: body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).' }) - name: Upload Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: storybook path: packages/frontend/storybook-static diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 9c54b3665b..77bbdb2b0a 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -18,18 +18,27 @@ on: - packages/misskey-js/** - .github/workflows/test-backend.yml - .github/misskey/test.yml + workflow_dispatch: + inputs: + force_ffmpeg_cache_update: + description: 'Force update ffmpeg cache' + required: false + default: false + type: boolean + jobs: unit: name: Unit tests (backend) runs-on: ubuntu-latest - strategy: matrix: - node-version: [22.11.0] + node-version-file: + - .node-version + - .github/min.node-version services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: @@ -39,14 +48,36 @@ jobs: image: redis:7 ports: - 56312:6379 + meilisearch: + image: getmeili/meilisearch:v1.3.4 + ports: + - 57712:7700 + env: + MEILI_NO_ANALYTICS: true + MEILI_ENV: development steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 + - name: Get current date + id: current-date + run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Setup and Restore ffmpeg/ffprobe Cache + id: cache-ffmpeg + uses: actions/cache@v4 + with: + path: | + /usr/local/bin/ffmpeg + /usr/local/bin/ffprobe + # daily cache + key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }} + restore-keys: | + ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }} - name: Install FFmpeg + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true run: | for i in {1..3}; do echo "Attempt $i: Installing FFmpeg..." @@ -61,10 +92,10 @@ jobs: exit 1 fi done - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: ${{ matrix.node-version-file }} cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml @@ -84,14 +115,16 @@ jobs: e2e: name: E2E tests (backend) runs-on: ubuntu-latest - strategy: + fail-fast: false matrix: - node-version: [22.11.0] + node-version-file: + - .node-version + - .github/min.node-version services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: @@ -103,15 +136,15 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: ${{ matrix.node-version-file }} cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml @@ -127,3 +160,47 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ./packages/backend/coverage/coverage-final.json + + migration: + name: Migration tests (backend) + runs-on: ubuntu-latest + strategy: + matrix: + node-version-file: + - .node-version + #- .github/min.node-version + + services: + postgres: + image: postgres:18 + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + + steps: + - uses: actions/checkout@v6.0.1 + with: + submodules: true + - name: Setup pnpm + uses: pnpm/action-setup@v4.2.0 + - name: Get current date + id: current-date + run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Use Node.js + uses: actions/setup-node@v6.1.0 + with: + node-version-file: ${{ matrix.node-version-file }} + cache: 'pnpm' + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .github/misskey/test.yml .config + - name: Build + run: pnpm build + - name: Run migrations + run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend migrate + - name: Check no migrations are remaining + run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend check-migrations diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml index dc29de4d4f..7f8fe547e1 100644 --- a/.github/workflows/test-federation.yml +++ b/.github/workflows/test-federation.yml @@ -14,6 +14,13 @@ on: - packages/backend/** - packages/misskey-js/** - .github/workflows/test-federation.yml + workflow_dispatch: + inputs: + force_ffmpeg_cache_update: + description: 'Force update ffmpeg cache' + required: false + default: false + type: boolean jobs: test: @@ -21,14 +28,31 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [22.11.0] + node-version-file: + - .node-version + - .github/min.node-version steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 + - name: Get current date + id: current-date + run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Setup and Restore ffmpeg/ffprobe Cache + id: cache-ffmpeg + uses: actions/cache@v4 + with: + path: | + /usr/local/bin/ffmpeg + /usr/local/bin/ffprobe + # daily cache + key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }} + restore-keys: | + ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }} - name: Install FFmpeg + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true run: | for i in {1..3}; do echo "Attempt $i: Installing FFmpeg..." @@ -43,10 +67,10 @@ jobs: exit 1 fi done - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: ${{ matrix.node-version-file }} cache: 'pnpm' - name: Build Misskey run: | @@ -54,6 +78,7 @@ jobs: pnpm build - name: Setup run: | + echo "NODE_VERSION=$(cat ${{ matrix.node-version-file }})" >> $GITHUB_ENV cd packages/backend/test-federation bash ./setup.sh sudo chmod 644 ./certificates/*.test.key @@ -71,18 +96,16 @@ jobs: docker compose logs | tail -n 300 exit 1 - name: Test - id: test - continue-on-error: true run: | cd packages/backend/test-federation docker compose run --no-deps tester - name: Log - if: ${{ steps.test.outcome == 'failure' }} + if: always() run: | cd packages/backend/test-federation docker compose logs - exit 1 - name: Stop servers + if: always() run: | cd packages/backend/test-federation docker compose down diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index bec5169ed9..52723e894c 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -27,20 +27,16 @@ jobs: name: Unit tests (frontend) runs-on: ubuntu-latest - strategy: - matrix: - node-version: [22.11.0] - steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml @@ -64,12 +60,11 @@ jobs: strategy: fail-fast: false matrix: - node-version: [22.11.0] browser: [chrome] services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: @@ -81,7 +76,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true # https://github.com/cypress-io/cypress-docker-images/issues/150 @@ -91,11 +86,11 @@ jobs: #- uses: browser-actions/setup-firefox@latest # if: ${{ matrix.browser == 'firefox' }} - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Copy Configure @@ -118,12 +113,12 @@ jobs: wait-on: 'http://localhost:61812' headed: true browser: ${{ matrix.browser }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 if: failure() with: name: ${{ matrix.browser }}-cypress-screenshots path: cypress/screenshots - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 if: always() with: name: ${{ matrix.browser }}-cypress-videos diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 2d1bd20183..428cbce3b8 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -20,22 +20,17 @@ jobs: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [22.11.0] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.1 - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + - name: Setup Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index b77550a01f..9c0ea4d738 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -15,20 +15,16 @@ jobs: name: Production build runs-on: ubuntu-latest - strategy: - matrix: - node-version: [22.11.0] - steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index 4023815cb1..8ffc60fc6e 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -16,20 +16,16 @@ jobs: validate-api-json: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [22.11.0] - steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4.1.0 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.3.0 + uses: pnpm/action-setup@v4.2.0 + - name: Use Node.js + uses: actions/setup-node@v6.1.0 with: - node-version: ${{ matrix.node-version }} + node-version-file: '.node-version' cache: 'pnpm' - name: Install Redocly CLI run: npm i -g @redocly/cli diff --git a/.gitignore b/.gitignore index ac7502f384..7839e4de66 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ docker-compose.yml built built-test js-built +src-js /data /.cache-loader /db diff --git a/.node-version b/.node-version index 7af24b7ddb..b8ffd70759 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.11.0 +22.15.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index 0ceec23acd..2d11d24db2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,11 +3,16 @@ "**/node_modules": true }, "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, "files.associations": { "*.test.ts": "typescript" }, - "jest.jestCommandLine": "pnpm run jest", "jest.runMode": "on-demand", + "jest.virtualFolders": [ + { "name": "backend unit", "jestCommandLine": "pnpm -F backend run test" }, + { "name": "backend e2e", "jestCommandLine": "pnpm -F backend run test:e2e"}, + { "name": "misskey-js", "jestCommandLine": "pnpm -F misskey-js run jest" } + ], "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0524d3c5bb..6258805f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,526 @@ +## 2026.1.0 + +### Note +- `users/following` の `birthday` プロパティは非推奨になりました。代わりに `users/get-following-birthday-users` をご利用ください。 + +### General +- Enhance: 「もうすぐ誕生日のユーザー」ウィジェットで、誕生日が至近のユーザーも表示できるように + (Cherry-picked from https://github.com/MisskeyIO/misskey) + - 「今日誕生日のユーザー」は「もうすぐ誕生日のユーザー」に名称変更されました +- 依存関係の更新 + +### Client +- Enhance: ドライブのファイル一覧で自動でもっと見るを利用可能に +- Enhance: ウィジェットの表示設定をプレビューを見ながら行えるように +- Enhance: ウィジェットの設定項目のラベルの多言語対応 +- Enhance: 画面幅が広いときにメディアを横並びで表示できるようにするオプションを追加 +- Enhance: パフォーマンスの向上 +- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061 +- Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正 +- Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正 +- Fix: 高度なMFMのピッカーを使用する際の挙動を改善 +- Fix: 管理画面でアーカイブ済のお知らせを表示した際にアクティブなお知らせが多い旨の警告が出る問題を修正 +- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正 +- Fix: 2月29日を誕生日に設定している場合、閏年以外は3月1日を誕生日として扱うように修正 +- Fix: `Mk:C:container` の `borderWidth` が正しく反映されない問題を修正 + +### Server +- Enhance: OAuthのクライアント情報取得(Client Information Discovery)において、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました + - JSONによるClient Information Discoveryを行うには、レスポンスの`Content-Type`ヘッダーが`application/json`である必要があります + - 従来の実装(12 February 2022版・HTML Microformat形式)も引き続きサポートされます +- Enhance: メモリ使用量を削減 + +## 2025.12.2 + +### Note +v2025.12.0で行われた「configの`trustProxy`のデフォルト値を`false`に変更」について、正しく環境に応じた設定を行わないとサインインが困難になるといった状態を緩和するために、以下の対応を行いました。 + +**正しく設定しないと、上記のような不具合の原因となったり、セキュリティリスクが高まったりする可能性があります。必ず現在のconfigをご確認の上、必要に応じて値を変更してください。** + +- `trustProxy`について、デフォルト(configに値が設定されていない状態)ではループバックアドレスとローカルIPアドレス空間を信頼するようにしました。 +- `trustProxy`の設定方法について、より詳細に記述しました。 +- リバースプロキシやCDNなどのより上流のレイヤでレートリミットを設定したい場合や、緊急時の一時的な緩和策として、Misskey内部でのIPアドレスペースでのレートリミットを無効化できるようにしました。 + +### General +- 依存関係の更新 + +### Client +- Enhance: デッキのUI説明を追加 +- Enhance: 設定がブラウザによって消去されないようにするオプションを追加 +- Fix: バージョン表記のないPlayが正しく動作しない問題を修正 + バージョン表記のないものは v0.x 系として実行されます。v1.x 系で動作させたい場合は必ずバージョン表記を含めてください。 +- Fix: デッキUIでメニュー位置を下にしているとプロファイル削除ボタンが表示されないのを修正 +- Fix: 一部のUnicode絵文字のリアクションがボタンにならない問題を修正 + +### Server +- Enhance: Misskey内部でのIPアドレスペースでのレートリミットを無効化できるように + - リバースプロキシやCDNなど別のレイヤで別途レートリミットを設定する場合や、ローカルでのテスト用途等として利用することを想定しています。 + - デフォルトは `enableIpRateLimit: true`(Misskey内部でのIPアドレスペースでのレートリミットは有効)です。 +- Fix: コントロールパネルのジョブキューページで使用される一部APIの応答速度を改善 + +## 2025.12.1 + +### Client +- Fix: 特定の条件下でMisskeyが起動せず空白のページが表示されることがある問題を軽減 +- Fix: 初回読み込み時などに、言語設定で不整合が発生することがある問題を修正 +- Fix: 削除されたノートのリノートが正しく動作されない問題を修正 +- Fix: チャンネルオーナーが削除済みの時にチャンネルのヘッダーメニューが表示されない不具合を修正 +- Fix: ドライブで登録日以外でソートする場合は月でグループ化して表示しないように +- Fix: `null` を返す note_view_intrruptor プラグインが動作しない問題を修正 + +### Server +- Fix: ジョブキューでSentryが有効にならない問題を修正 + + +## 2025.12.0 + +### Note +- configの`trustProxy`のデフォルト値を`false`に変更しました。アップデート前に現在のconfigをご確認の上、必要に応じて値を変更してください。 + +### Client +- Fix: stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正 + +### Server +- Enhance: メモリ使用量を削減しました +- Enhance: ActivityPubアクティビティを送信する際のパフォーマンス向上 +- Enhance: 依存関係の更新 +- Fix: セキュリティに関する修正 + +## 2025.11.1 + +### Client + +- Enhance: リアクションの受け入れ設定にキャプションを追加 #15921 +- Fix: ページの内容がはみ出ることがある問題を修正 +- Fix: ナビゲーションバーを下に表示しているときに、項目数が多いと表示が崩れる問題を修正 +- Fix: ヘッダーメニューのチャンネルの新規作成の項目でチャンネル作成ページに飛べない問題を修正 #16816 +- Fix: ラジオボタンに空白の選択肢が表示される問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1105) +- Fix: 一部のシチュエーションで投稿フォームのツアーが正しく表示されない問題を修正 +- Fix: 投稿フォームのリセットボタンで注釈がリセットされない問題を修正 +- Fix: PlayのAiScriptバージョン判定(v0.x系・v1.x系の判定)が正しく動作しない問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1129) +- Fix: フォロー申請をキャンセルする際の確認ダイアログの文言が不正確な問題を修正 +- Fix: 初回読み込み時にエラーになることがある問題を修正 +- Fix: お気に入りクリップの一覧表示が正しく動作しない問題を修正 +- Fix: AiScript Misskey 拡張APIにおいて、各種関数の引数で明示的に `null` が指定されている場合のハンドリングを修正 + +### Server +- Enhance: メモリ使用量を削減しました +- Enhance: 依存関係の更新 +- Fix: ワードミュートの文字数計算を修正 +- Fix: チャンネルのリアルタイム更新時に、ロックダウン設定にて非ログイン時にノートを表示しない設定にしている場合でもノートが表示されてしまう問題を修正 +- Fix: DeepL APIのAPIキー指定方式変更に対応 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1096) + - 内部実装の変更にて対応可能な更新です。Misskey側の設定方法に変更はありません。 +- Fix: DBレプリケーションを利用する環境でクエリーが失敗する問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1123) + +## 2025.11.0 + +### General +- Feat: チャンネルミュート機能の実装 #10649 + - チャンネルの概要画面の右上からミュートできます(リンクコピー、共有、設定と同列) +- Enhance: Node.js 24.10.0をサポートするようになりました +- Enhance: DockerのNode.jsが24.10.0に更新されました +- 依存関係の更新 + +### Client +- Feat: 画像にメタデータを含むフレームをつけられる機能 +- Enhance: プリセットを作成しなくても画像にウォーターマークを付与できるように +- Enhance: 管理しているチャンネルの見分けがつきやすくなるように +- Enhance: プロフィールへのリンクをユーザーポップアップのアバターに追加 +- Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加 +- Enhance: プッシュ通知を行うための権限確認をより確実に行うように +- Enhance: 投稿フォームのチュートリアルを追加 +- Enhance: 「自動でもっと見る」をほとんどの箇所で利用可能に +- Enhance: アンテナ・リスト設定画面とタイムラインの動線を改善 + - アンテナ・リスト一覧画面の項目を選択すると、設定画面ではなくタイムラインに移動するようになりました + - アンテナ・リストの設定画面の右上にタイムラインに移動するボタンを追加しました +- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正 +- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように +- Fix: ページのタイトルが長いとき、はみ出る問題を修正 +- Fix: 投稿フォームのアバターが正しく表示されない問題を修正 #16789 +- FIx: カスタム絵文字(β)画面で変更行が正しくハイライトされない問題を修正 #16626 + +### Server +- Enhance: Remote Notes Cleaningが複雑度が高いノートの処理を中断せずに次のノートから再開するように +- Fix: チャンネルの説明欄の最小文字数制約を除去 + +## 2025.10.2 + +### Client +- Fix: アプリ内からキャッシュをクリアするとテーマ再適用するまでレンダリングが正しく行われない問題を修正 +- Fix: 期限が無期限のアンケートに投票できない問題を修正 + +## 2025.10.1 + +### General +- Enhance: リモートユーザーに付与したロールバッジを表示できるように(オプトイン) + パフォーマンス上の問題からデフォルトで無効化されています。「コントロールパネル > パフォーマンス」から有効化できます。 +- 依存関係の更新 + +### Client +- Enhance: デッキのメインカラムのヘッダをクリックしてページ上部/下部にスクロールできるように +- Enhance: 下書き/予約投稿一覧は投稿フォームのアカウントメニュー内に移動し、下書き保存は「...」メニュー内に移動されました +- Fix: カスタム絵文字画面(beta)のaliasesで使用される区切り文字が一致していないのを修正 #15614 +- Fix: バナー画像の幅が表示領域と一致していない問題を修正 +- Fix: 一部のブラウザでバナー画像が上下中央に表示されない問題を修正 +- Fix: ナビゲーションバーの設定で削除した項目をその場で再追加できない問題を修正 +- Fix: ロールポリシーによりダイレクトメッセージが無効化されている際のデッキのダイレクトメッセージカラムの挙動を改善 +- Fix: 画像のマスクでタッチ操作が不安定な問題を修正 +- Fix: ウォーターマークの各種挙動修正 + - ウォーターマークを回転させると歪む問題を修正 + - ウォーターマークを敷き詰めると上下左右反転した画像/文字が表示される問題を修正 + - ウォーターマークを回転させた際に画面からはみ出た部分を考慮できるように +- Fix: 投票が終了した後に投票結果が正しく表示されない問題を修正 +- Fix: ダークモードの同期が機能しない場合がある問題を修正 +- Fix: iOSで動画の圧縮を行うと音声トラックが失われる問題を修正 + +### Server +- Enhance: 管理者/モデレーターはファイルのアップロード制限をバイパスするように +- Enhance: セキュリティの向上 + +## 2025.10.0 + +### NOTE +- pnpm 10.16.0 が必要です +- ロールのインポート機能の利用可否ポリシーのデフォルト値が「いいえ」に変わったため、デフォルトから変更していないサーバーでは適宜設定を変更してください。 +- ロールのアップロード可能なファイル種別ポリシーのデフォルト値に「text/*」が追加されたため、デフォルトから変更していないサーバーでは適宜設定を変更してください。 + +### General +- Feat: 予約投稿ができるようになりました + - デフォルトで作成可能数は1になっています。適宜ロールのポリシーで設定を行ってください。 +- Enhance: 広告ごとにセンシティブフラグを設定できるようになりました +- Enhance: 依存関係の更新 +- Enhance: 翻訳の更新 + +### Client +- Feat: アカウントのQRコードを表示・読み取りできるようになりました +- Feat: 動画を圧縮してアップロードできるようになりました +- Feat: (実験的) ブラウザ上でノートの翻訳を行えるように +- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました +- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし、モザイク)を追加 +- Enhance: 画像編集の集中線エフェクトを強化 +- Enhance: ウォーターマークにアカウントのQRコードを追加できるように +- Enhance: テーマをドラッグ&ドロップできるように +- Enhance: 絵文字ピッカーのサイズをより大きくできるように +- Enhance: カスタム絵文字が多い場合にサーバーの絵文字一覧ページがフリーズしないように +- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 +- Enhance: 「お問い合わせ」ページから、バグの調査等に役立つ情報(OSやブラウザのバージョン等)を取得・コピーできるように +- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正 +- Fix: アクティビティウィジェットのグラフモードが動作しない問題を修正 +- Fix: ユニコード絵文字の追加辞書をインストールするとユニコード絵文字が絵文字ピッカーで検索できなくなる絵文字があるバグを修正 + +### Server +- Enhance: ユーザーIPを確実に取得できるために設定ファイルにFastifyOptions.trustProxyを追加しました + +## 2025.9.0 + +### Client +- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました +- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張 +- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように +- Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように +- Fix: プッシュ通知を有効にできない問題を修正 +- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 +- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正 +- Fix: エラー画像が横に引き伸ばされてしまう問題に対応 + +### Server +- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正 + +## 2025.8.0 + +### Note +- サポートされるNode.jsの最小バージョンが**22.15.0**になりました + +### General +- ノートを削除した際、関連するノートが同時に削除されないようになりました + - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります +- 定期的に古いリモートの投稿を削除する機能が実装されました + - コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます + - データベースの肥大化を防止することが可能です + - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 + - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 + - データベースサイズへの効果が見られない場合はautovacuumが有効になっているか確認してください +- サーバーの初期設定が完了するまでは連合がオンにならないようになりました +- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました + - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが(過去のバージョンのMisskeyでも、当該機能は「チャット」ではなく「ダイレクトメッセージ」でした)、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 今後、「チャット」の名称を「ダイレクトメッセージ」に戻す可能性があります +- mfm.jsをアップデートしました + - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 + - Enhance: acctに `.` が入っているユーザーのメンションに対応 + - Fix: Unicode絵文字に隣接する異体字セレクタ(`U+FE0F`)が絵文字として認識される問題を修正 +- Enhance: ユーザー検索をロールポリシーで制限できるように + +### Client +- Feat: AiScriptが1.1.0に更新されました + - プラグインは1.xに対応したものが必要です + - Playはそのまま動作しますが、新規に作られるプリセットは1.xになります + - 以前のバージョンから無効化されていた note_view_interruptor が有効になりました + - ハンドラは同期的である必要があります +- Feat: セーフモード + - プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます + - 以下の方法でセーフモードを起動できます + - `g` キーを連打する + - URLに`?safemode=true`を付ける + - PWAのショートカットで Safemode を選択して起動する +- Feat: 非ログイン時に表示されるトップページのスタイルを選択できるように + - コントロールパネル→ブランディング→エントランスページのスタイル +- Feat: ページのタブバーを下部に表示できるように +- Feat: (実験的)iOSでの触覚フィードバックを有効にできるように +- Feat: コントロールパネルを検索できるように +- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました +- Enhance: トルコ語 (tr-TR) に対応 +- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました +- Enhance: 画像エフェクトのパラメータ名の多言語対応 +- Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように +- Enhance: メールアドレス確認画面のUIを改善 +- Enhance: アイコンのスクロール追従を無効化する際の適用範囲を強化 +- Enhance: レンダリングパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 +- Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 +- Fix: 一部の設定検索結果が存在しないパスになる問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) +- Fix: テーマエディタが動作しない問題を修正 +- Fix: チャンネルのハイライトページにノートが表示されない問題を修正 +- Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 +- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 +- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 +- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 +- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 +- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 +- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正 +- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 +- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 + +### Server +- Feat: サーバー管理コマンド + - `pnpm cli foo` の形式で実行可能です + - 現在以下のコマンドが利用可能です + - `reset-captcha` - CAPTCHA設定をリセットします +- Enhance: ノートの削除処理の効率化 +- Enhance: 全体的なパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 +- Enhance: `clips/list` APIがページネーションに対応しました +- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 +- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 +- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正 +- Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように +- Fix: タイムラインAPIの withRenotes: false 時のレスポンスを修正 + + +## 2025.7.0 + +### Note +- Node.jsの最小バージョンを20.10.0から20.18.1に引き上げました + - なお、特に必要がない限りNode.jsは推奨バージョンであるv22を使用するようにしてください + +### General +- Feat: ノートの下書き機能 +- Feat: クリップ内でノートを検索できるように +- Feat: Playを検索できるように +- Feat: モデレーションにおいて、特定のドライブファイルを添付しているチャットメッセージを一覧できるように +- Enhance: ウォーターマーク機能をロールで制御可能に + +### Client +- Note: 「自動でもっと見る」オプションは無効になっています +- Feat: モデログを検索できるように +- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように +- Enhance: ファイルアップロード前にキャプション設定を行えるように +- Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように +- Enhance: 投稿フォームにファイルをペースト/ドロップした際のUXを改善 +- Enhance: ページネーション(一覧表示)の並び順を逆にできるように +- Enhance: ページネーション(一覧表示)の基準日時を指定できるように +- Enhance: レンダリングパフォーマンスの向上 +- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正 +- Fix: プラグインをアンインストールしてもセーブデータが残る問題を修正 +- Fix: 数時間後Misskeyのタブに戻った際に、タブがスロットリングされている間の更新アニメーションを延々見せ続けられる問題を修正 +- Fix: 非ログイン時のハイライトノートの画像がCWの有無を考慮せず表示される問題を修正 +- Fix: レンジ選択・ドロップダウンにて、操作を無効にすべきところで無効にならない問題を修正 +- Fix: Pull to refreshが有効なときに横スクロールができない問題を修正 + +### Server +- Enhance: sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に +- Enhance: メールの送信者としてサーバー名を表示するように (サーバー名が設定されている場合) +- Fix: ジョブキューのProgressの値を正しく計算する + + +## 2025.6.3 + +### Client +- Fix: キャッシュを削除しないとクライアントが使用できないことがある問題を修正 + +## 2025.6.2 + +### Client +- Fix: キャッシュを削除しないとクライアントが使用できないことがある問題を修正 +- 翻訳の更新 + +## 2025.6.1 + +### Note +- AiScript Misskey拡張API(Misskey Webプラグイン)の[note_view_interruptor](https://misskey-hub.net/ja/docs/for-developers/plugin/plugin-api-reference/#pluginregister_note_view_interruptorfn)は不具合の影響により現在一時的に無効化されています。 +- Misskey Web投稿フォームのプレビュー切り替えは「...」メニュー内に配置されました + +### Client +- Feat: 画像にウォーターマークを付与できるようになりました +- Feat: 画像の加工ができるようになりました(実験的) +- Enhance: ノートのリアクション一覧で、押せるリアクションを優先して表示できるようにするオプションを追加 +- Enhance: 全てのチャットメッセージを既読にできるように(設定→その他) +- Enhance: ミュートした絵文字をデバイス間で同期できるように +- Fix: ドライブファイルの選択が不安定な問題を修正 +- Fix: コントロールパネルのファイル欄などのデザインが崩れている問題を修正 +- Fix: ユーザーの検索結果を追加で読み込むことができない問題を修正 +- Fix: タッチ操作時にチャートのツールチップが消えなくなる場合がある問題を修正 +- Fix: ウェルカムタイムラインでリアクションが表示されない問題を修正 +- Fix: デッキのタイムラインカラムで新着ノート時のサウンドが再生されない問題を修正 + +### Server +- Feat: 全てのチャットメッセージを既読にするAPIを追加(chat/read-all) +- Fix: アカウント削除が正常に行われないことがあった問題を修正 +- Fix: outboxのページネーションが正しく行われない問題を修正 + +### Misskey.js +- Fix: misskey-jsの drive/file/create でファイルアップロードができない問題を修正 + +## 2025.6.0 + +### Client +- Enhance: 非同期的なコンポーネントの読み込み時のハンドリングを強化 +- Fix: リアクションの一部の絵文字が重複して表示されることがある問題を修正 +- Fix: 非利用者に対するユーザー作成コンテンツの公開範囲が全て非公開になっている場合にログインできない問題を修正 + +### Server +- Fix: 非利用者に対するユーザー作成コンテンツの公開範囲が全て非公開になっている場合でもusers/showを許可するように + +## 2025.5.1 + +### Note +- 設定ファイルの以下の項目がコントロールパネルから設定するようになりました + - signToActivityPubGet + - proxyRemoteFiles + - disallowExternalApRedirect + - 許可しないかどうかではなく、許可するかどうかの設定(allowExternalApRedirect)になりました + +### General +- Feat: 非ログインでサーバーを閲覧された際に、サーバー内のコンテンツを非公開にすることができるようになりました + - モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます + - 「全て公開(今までの挙動)」「ローカルのコンテンツだけ公開(=サーバー内で受信されたリモートのコンテンツは公開しない)」「何も公開しない」から選択できます + - デフォルト値は「ローカルのコンテンツだけ公開」になっています +- Feat: ロールでアップロード可能なファイル種別を設定可能になりました + - デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。 + - 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。 + - したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。 +- Feat: プレビュー先がリダイレクトを伴う場合、リダイレクト先のコンテンツを取得しに行くか否かを設定できるように(#16043) +- Enhance: UIのアイコンデータの読み込みを軽量化 + +### Client +- Feat: ドライブのUIが強化されました + - 複数のファイルをまとめて移動できるようになりました +- Feat: ファイルのアップロードUIが一新されました + - アップロード前にファイル情報を確認できるようになりました + - 圧縮の品質を選択できるようになりました + - アップロードに失敗したときに再試行できるようになりました + - アップロード前に画像のクロッピングを行えるようになりました + - ファイルサイズのチェックは圧縮後の実際にアップロードされるサイズで行われるようになりました + - ファイルのアップロードを中断できるようになりました +- Feat: サーバー初期設定ウィザードが実装されました + - 簡単なウィザードに従うだけで、サーバーに最適な設定が適用されます +- Feat: Websocket接続を行わずにMisskeyを利用するNo Websocketモードが実装されました(beta) + - サーバーのパフォーマンス向上に寄与することが期待されます + - 何らの理由によりWebsocket接続が行えない環境でも快適に利用可能です + - 従来のWebsocket接続を行うモードはリアルタイムモードとして再定義されました + - チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます +- Feat: 絵文字をミュート可能にする機能 + - 絵文字(ユニコードの絵文字・カスタム絵文字)毎にミュートし、不可視化することができるようになりました +- Feat: モバイルデバイスで折りたたまれたUIの展開表示に全画面ページを使用できるように(実験的) +- Enhance: 設定の同期をオンにするときに競合したときに値をマージできるように +- Enhance: メモリ使用量を軽減しました +- Enhance: 画像の高品質なプレースホルダを無効化してパフォーマンスを向上させるオプションを追加 +- Enhance: 招待されているが参加していないルームを開いたときに、招待を承認するかどうか尋ねるように +- Enhance: リプライ元にアンケートがあることが表示されるように +- Enhance: ノートのサーバー情報のデザインを改善・パフォーマンス向上 + (Based on https://github.com/taiyme/misskey/pull/198, https://github.com/taiyme/misskey/pull/211, https://github.com/taiyme/misskey/pull/283) +- Enhance: ユーザー設定でURLプレビューを無効化できるように +- Enhance: ヒントとコツを追加 +- Enhance: ヒントとコツを再表示できるように +- Enhance: AiScriptからtoastを表示する関数 `Mk:toast` を追加 +- Enhance: シンタックスハイライトのエンジンをJavaScriptベースのものに変更 + - フロントエンドの読み込みサイズを軽量化しました + - ほとんどの言語のハイライトは問題なく行えますが、互換性の問題により一部の言語が正常にハイライトできなくなる可能性があります。詳しくは https://shiki.style/references/engine-js-compat をご覧ください。 +- Fix: チャットに動画ファイルを送付すると、動画の表示が崩れてしまい視聴出来ない問題を修正 +- Fix: アカウント依存かつ初期状態である設定値をサーバー同期しようとした際に正しくコンフリクト検出されない問題を修正 +- Fix: "時計"ウィジェット(Clock)において、Transparent設定が有効でも、その背景が透過されない問題を修正 +- Fix: 一定時間操作がなかったら動画プレイヤーのコントロールを隠すように +- Fix: Twitchのクリップがプレイヤーで再生できない問題を修正 + +### Server +- Enhance: リストやフォローをエクスポートする際にリプライを含むかどうかの情報を含むように +- Enhance: チャットルームの最大メンバー数を30人から50人に調整 +- Enhance: ノートのレスポンスにアンケートが添付されているかどうかを示すフラグ`hasPoll`を追加 +- Enhance: チャットルームのレスポンスに招待されているかどうかを示すフラグ`invitationExists`を追加 +- Enhance: レートリミットの計算方法を調整 (#13997) +- Enhance: 外部サイトのOGPのキャッシュ期間を調整 +- Fix: チャットルームが削除された場合・チャットルームから抜けた場合に、未読状態が残り続けることがあるのを修正 +- Fix: ユーザ除外アンテナをインポートできない問題を修正 +- Fix: アンテナのセンシティブなチャンネルのノートを含むかどうかの情報がエクスポートされない問題を修正 +- Fix: ミュート対象ユーザーが引用されているノートがRNされたときにミュートを貫通してしまう問題を修正 #16009 +- Fix: 連合モードが「なし」の場合に、生成されるHTML内のactivity jsonへのリンクタグを省略するように +- Fix: コントロールパネルから招待コードを作成すると作成者の情報が記録されない問題を修正 +- Fix: コントロールパネルのジョブキューページからPausedなジョブ一覧を閲覧できない問題を修正 + +## 2025.5.0 + +### Note +- DockerのNode.jsが22.15.0に更新されました + +### Client +- Feat: マウスで中ボタンドラッグによりタイムラインを引っ張って更新できるように + - アクセシビリティ設定からオフにすることもできます +- Enhance: タイムラインのパフォーマンスを向上 +- Enhance: バックアップされた設定のプロファイルを削除できるように +- Fix: 一部のブラウザでアコーディオンメニューのアニメーションが動作しない問題を修正 +- Fix: ダイアログのお知らせが画面からはみ出ることがある問題を修正 +- Fix: ユーザーポップアップでエラーが生じてもインジケーターが表示され続けてしまう問題を修正 + +### Server +- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775` +- Enhance: 連合先のソフトウェア及びバージョン名により配信停止を行えるように `#15727` +- Enhance: 2025.4.1 で追加されたインデックスの再生成をノートの追加しながら行えるようになりました。 `#15915` + - `MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY` 環境変数を `1` にセットしていると、巨大なテーブルの既存のカラムに関するインデックス再生成が`CREATE INDEX CONCURRENTLY`を使用するようになりました。 + - 複数のサーバープロセスをクラスタリングしているサーバーにおいて、一部のプロセスが起動している状態でこのオプションを有効にしてマイグレーションすることにより、ダウンタイムを削減することができます。 + - ただし、このオプションを有効にする場合、インデックスの作成にかかる時間が倍~3倍以上になることがあります。 + - また、大きなインスタンスである場合にはインデックスの作成に失敗し、複数回再試行する必要がある可能性があります。 +- Fix: チャンネルのフォロー一覧の結果が一部正しくないのを修正 (#12175) +- Fix: ファイルをアップロードした際にファイル名が常に untitled になる問題を修正 +- Fix: ファイルのアップロードに失敗することがある問題を修正 + - 投稿フォーム上で画像のクロップを行うと、`Invalid Param.`エラーでノートが投稿出来なくなる問題も解決されます。 + - この事象によって既にノートが投稿出来ない状態になっている場合は、投稿フォーム右上のメニューから、下書きデータの「リセット」を行ってください。 + ## 2025.4.1 ### General - Feat: bull-boardに代わるジョブキューの管理ツールが実装されました +- Feat: アップロード可能な最大ファイルサイズをロールごとに設定可能に + - デフォルトで10MBになっています - Enhance: チャットの新規メッセージをプッシュ通知するように +- Enhance: サーバーブロックの対象になっているサーバーについて、当該サーバーのユーザーや既知投稿を見えないように +- Enhance: 依存関係の更新 +- Enhance: 翻訳の更新 +- Fix: セキュリティに関する修正 ### Client - Feat: チャットウィジェットを追加 - Feat: デッキにチャットカラムを追加 +- Feat: タイトルバーを表示できるように - Enhance: Unicode絵文字をslugから入力する際に`:ok:`のように最後の`:`を入力したあとにUnicode絵文字に変換できるように - Enhance: コントロールパネルでジョブキューをクリアできるように - Enhance: テーマでページヘッダーの色を変更できるように +- Enhance: スワイプでのタブ切り替えを強化 - Enhance: デザインのブラッシュアップ - Fix: ログアウトした際に処理が終了しない問題を修正 - Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように @@ -17,14 +528,17 @@ - Fix: タイムラインのスクロール位置を記憶するように修正 - Fix: ノートの直後のノートを表示する機能で表示が逆順になっていた問題を修正 #15841 - Fix: アカウントの移行時にアンテナのフィルターのユーザが更新されない問題を修正 #15843 +- Fix: タイムラインでノートが重複して表示されることがあるのを修正 ### Server - Enhance: ジョブキューの成功/失敗したジョブも一定数・一定期間保存するようにし、後から問題を調査することを容易に - Enhance: フォローしているユーザーならフォロワー限定投稿のノートでもアンテナで検知できるように (Cherry-picked from https://github.com/yojo-art/cherrypick/pull/568 and https://github.com/team-shahu/misskey/pull/38) +- Enhance: ユーザーごとにノートの表示が高速化するように - Fix: システムアカウントの名前がサーバー名と同期されない問題を修正 -- Fix: 大文字を含むユーザの URL で紹介された場合に 404 エラーを返す問題 #15813 +- Fix: 大文字を含むユーザの URL で照会された場合に 404 エラーを返す問題 #15813 - Fix: リードレプリカ設定時にレコードの追加・更新・削除を伴うクエリを発行した際はmasterノードで実行されるように調整( #10897 ) +- Fix: ファイルアップロード時の挙動を一部調整(#15895) ## 2025.4.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3aedfa9eb..95e6077d93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -258,6 +258,12 @@ Misskey uses Vue(v3) as its front-end framework. - **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.** - Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. +## Tabler Icons +アイコンは、Production Build時に使用されていないものが削除されるようになっています。 + +**アイコンを動的に設定する際には、 `ti-${someVal}` のような、アイコン名のみを動的に変化させる実装を行わないでください。** +必ず `ti-xxx` のような完全なクラス名を含めるようにしてください。 + ## nirax niraxは、Misskeyで使用しているオリジナルのフロントエンドルーティングシステムです。 **vue-routerから影響を多大に受けているので、まずはvue-routerについて学ぶことをお勧めします。** @@ -575,27 +581,6 @@ pnpm dlx typeorm migration:generate -d ormconfig.js -o - 生成後、ファイルをmigration下に移してください - 作成されたスクリプトは不必要な変更を含むため除去してください -### JSON SchemaのobjectでanyOfを使うとき -JSON Schemaで、objectに対してanyOfを使う場合、anyOfの中でpropertiesを定義しないこと。 -バリデーションが効かないため。(SchemaTypeもそのように作られており、objectのanyOf内のpropertiesは捨てられます) -https://github.com/misskey-dev/misskey/pull/10082 - -テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合: - -```ts -export const paramDef = { - type: 'object', - properties: { - hoge: { type: 'string', minLength: 1 }, - fuga: { type: 'string', minLength: 1 }, - }, - anyOf: [ - { required: ['hoge'] }, - { required: ['fuga'] }, - ], -} as const; -``` - ### コネクションには`markRaw`せよ **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 @@ -633,3 +618,23 @@ color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); color: color(from var(--MI_THEME-accent) srgb r g b / 0.5); ``` +## 考え方 +### DRYに囚われるな +必要なのは一般化ではなく抽象化と考えます。 +盲信せず、誤った・不必要な共通化は避け、それが自然だと感じる場合は重複させる勇気を持ちましょう。 + +### Misskeyを複雑にしない実装 +それがいくら複雑であっても、Misskey固有のコンテキストと関心が分離されている(もしくは事実上分離されていると見做すことができる)実装であれば、それはMisskeyのコードベースに対する複雑性に影響を与えないと考えます。 + +例えるなら、VueやAiScriptといったMisskeyが使用しているライブラリの内部実装がいくら複雑だったとしても、「それを使用しているからMisskeyの実装は複雑である」ということにはならないのと同じです。 + +Misskeyのドメイン知識から関心が分離されているということは、Misskeyの実装について考える時にそれらの内部実装を考慮する必要が無く、認知負荷を増やさないからです。 + +また重要な点は、その実装が、Misskeyリポジトリの外部にあるか・内部にあるかということや、Misskeyがメンテナンスするものか・第三者がメンテナンスするものかといったことは複雑性を考える上ではほとんど無視できるという点です。 + +もちろんその実装がMisskeyリポジトリにあり、Misskeyがメンテナンスしなければならないものは、保守のコストはかかります。 +しかし、Misskeyの本質的な設計・実装という観点で見たときは、その実装は実質的に外部ライブラリのように振る舞います。 +換言すれば「たまたまMisskeyの開発者と同じ人たちがメンテナンスしているし、たまたまMisskeyのリポジトリ内に置いてあるだけの外部ライブラリ」です。 + +そのため、実装をなるべくMisskeyのドメイン知識から独立したものにすれば、Misskeyのコードベースの複雑性を上げることなく機能実装を行うことができ、お得であると言えます。 +もちろんそれにこだわって、些細な実装でもそのように分離してしまうとかえって認知負荷が増えたり、実装量が増えてメリットをデメリットが上回る場合もあるので、ケースバイケースではあります。 diff --git a/COPYING b/COPYING index 7635bfc913..a17c82c002 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2025 syuilo and contributors +Copyright © 2014-2026 syuilo and contributors And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. diff --git a/Dockerfile b/Dockerfile index 26204873bb..6a053b93c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -# syntax = docker/dockerfile:1.4 +# syntax = docker/dockerfile:1.20 -ARG NODE_VERSION=22.11.0-bookworm +ARG NODE_VERSION=22.21.1-bookworm # build assets & compile TypeScript @@ -18,10 +18,14 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] +COPY --link ["patches", "./patches"] COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"] +COPY --link ["packages/frontend-builder/package.json", "./packages/frontend-builder/"] +COPY --link ["packages/i18n/package.json", "./packages/i18n/"] +COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"] COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] @@ -53,6 +57,7 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] +COPY --link ["patches", "./patches"] COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] @@ -101,6 +106,8 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-rev COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-mahjong/built ./packages/misskey-mahjong/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built +COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/src-js ./packages/backend/src-js +COPY --chown=misskey:misskey --from=native-builder /misskey/packages/i18n/built ./packages/i18n/built COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ diff --git a/README.md b/README.md index 92e8fef639..e3261d13c2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ become a patron +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/misskey-dev/misskey) + + + ## Thanks @@ -47,3 +51,13 @@ Thanks to [Crowdin](https://crowdin.com/) for providing the localization platfor Docker Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production. + +--- + +
+ +Support us with a ⭐ ! + +[![Star History Chart](https://api.star-history.com/svg?repos=misskey-dev/misskey&type=Date)](https://star-history.com/#misskey-dev/misskey&Date) + +
diff --git a/ROADMAP.md b/ROADMAP.md index 509ecb9fe7..7f5f268f2b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,7 +6,7 @@ Also, the later tasks are more indefinite and are subject to change as developme This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development. - ~~Make the number of type errors zero (backend)~~ → Done ✔️ -- Make the number of type errors zero (frontend) +- ~~Make the number of type errors zero (frontend)~~ → Done ✔️ - Improve CI - ~~Fix tests~~ → Done ✔️ - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 diff --git a/assets/ui-icons.afdesign b/assets/ui-icons.afdesign new file mode 100644 index 0000000000..39abf1dd4f Binary files /dev/null and b/assets/ui-icons.afdesign differ diff --git a/chart/files/default.yml b/chart/files/default.yml index 06f762aafa..8fa0b39eff 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -221,9 +221,6 @@ id: "aidx" # Media Proxy #mediaProxy: https://example.com/proxy -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true - #allowedPrivateNetworks: [ # '127.0.0.1/32' #] diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml index 3c73837801..280bf9a597 100644 --- a/chart/templates/Deployment.yml +++ b/chart/templates/Deployment.yml @@ -27,7 +27,7 @@ spec: ports: - containerPort: 3000 - name: postgres - image: postgres:15-alpine + image: postgres:18-alpine env: - name: POSTGRES_USER value: "example-misskey-user" diff --git a/compose.local-db.yml b/compose.local-db.yml index 3835cb23db..2b69cabf5a 100644 --- a/compose.local-db.yml +++ b/compose.local-db.yml @@ -15,13 +15,13 @@ services: db: restart: always - image: postgres:15-alpine + image: postgres:18-alpine ports: - "5432:5432" env_file: - .config/docker.env volumes: - - ./db:/var/lib/postgresql/data + - ./db:/var/lib/postgresql healthcheck: test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" interval: 5s diff --git a/compose_example.yml b/compose_example.yml index 336bd814a7..efc7d6624d 100644 --- a/compose_example.yml +++ b/compose_example.yml @@ -37,13 +37,13 @@ services: db: restart: always - image: postgres:15-alpine + image: postgres:18-alpine networks: - internal_network env_file: - .config/docker.env volumes: - - ./db:/var/lib/postgresql/data + - ./db:/var/lib/postgresql healthcheck: test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" interval: 5s diff --git a/cypress.config.ts b/cypress.config.ts index e390c41a54..361acaf6e5 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -2,11 +2,6 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require('./cypress/plugins/index.js')(on, config) - }, baseUrl: 'http://localhost:61812', }, }) diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts index 6471f96504..4f2f700146 100644 --- a/cypress/e2e/basic.cy.ts +++ b/cypress/e2e/basic.cy.ts @@ -31,6 +31,14 @@ describe('Before setup instance', () => { // なぜか動かない //cy.wait('@signup').should('have.property', 'response.statusCode'); cy.wait('@signup'); + + cy.intercept('POST', '/api/admin/update-meta').as('update-meta'); + + cy.get('[data-cy-next]').click(); + cy.get('[data-cy-server-name] input').type('Testskey'); + cy.get('[data-cy-server-setup-wizard-apply]').click(); + + cy.wait('@update-meta'); }); }); @@ -70,6 +78,8 @@ describe('After setup instance', () => { cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-submit]').should('be.disabled'); cy.get('[data-cy-signup-password-retype] input').type('alice1234'); + cy.get('[data-cy-signup-submit]').should('be.disabled'); + cy.get('[data-cy-signup-invitation-code] input').type('test-invitation-code'); cy.get('[data-cy-signup-submit]').should('not.be.disabled'); cy.get('[data-cy-signup-submit]').click(); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js deleted file mode 100644 index 59b2bab6e4..0000000000 --- a/cypress/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -// eslint-disable-next-line no-unused-vars -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} diff --git a/idea/MkAbuseReport.stories.impl.ts b/idea/MkAbuseReport.stories.impl.ts index 717bceb23d..138e5cd0cd 100644 --- a/idea/MkAbuseReport.stories.impl.ts +++ b/idea/MkAbuseReport.stories.impl.ts @@ -4,8 +4,8 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; -import { StoryObj } from '@storybook/vue3'; +import { action } from 'storybook/actions'; +import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js'; import { commonHandlers } from '../packages/frontend/.storybook/mocks.js'; diff --git a/idea/MkAnimatedBg.dotted-ripples.vue b/idea/MkAnimatedBg.dotted-ripples.vue new file mode 100644 index 0000000000..f8f809c8ce --- /dev/null +++ b/idea/MkAnimatedBg.dotted-ripples.vue @@ -0,0 +1,232 @@ + + + + + + + diff --git a/idea/MkAnimatedBg.dotted.vue b/idea/MkAnimatedBg.dotted.vue new file mode 100644 index 0000000000..7f68b8972a --- /dev/null +++ b/idea/MkAnimatedBg.dotted.vue @@ -0,0 +1,190 @@ + + + + + + + diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index adc37c2414..a72429e941 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -68,7 +68,7 @@ receiveFollowRequest: "تلقيت طلب متابعة" followRequestAccepted: "قُبل طلب المتابعة" mention: "أشر الى" mentions: "الإشارات" -directNotes: "الملاحظات المباشرة" +directNotes: "رسالة خاصة" importAndExport: "إستورد / صدر" import: "استيراد" export: "تصدير" @@ -215,7 +215,6 @@ noUsers: "ليس هناك مستخدمون" editProfile: "تعديل الملف التعريفي" noteDeleteConfirm: "هل تريد حذف هذه الملاحظة؟" pinLimitExceeded: "لا يمكنك تثبيت الملاحظات بعد الآن." -intro: "لقد انتهت عملية تنصيب Misskey. الرجاء إنشاء حساب إداري." done: "تمّ" processing: "المعالجة جارية" preview: "معاينة" @@ -676,7 +675,6 @@ experimental: "اختباري" developer: "المطور" makeExplorable: "أظهر الحساب في صفحة \"استكشاف\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\"" -showGapBetweenNotesInTimeline: "أظهر فجوات بين المشاركات في الخيط الزمني" left: "يسار" center: "وسط" wide: "عريض" @@ -1010,6 +1008,17 @@ lastNDays: "آخر {n} أيام" surrender: "ألغِ" postForm: "أنشئ ملاحظة" information: "عن" +inMinutes: "د" +inDays: "ي" +widgets: "التطبيقات المُصغّرة" +presets: "إعدادات مسبقة" +_imageEditing: + _vars: + filename: "اسم الملف" +_imageFrameEditor: + font: "الخط" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "دعوة" noHistory: "السجل فارغ" @@ -1254,7 +1263,6 @@ _theme: buttonBg: "خلفية الأزرار" buttonHoverBg: "خلفية الأزرار (عند التمرير فوقها)" inputBorder: "حواف حقل الإدخال" - driveFolderBg: "خلفية مجلد قرص التخزين" messageBg: "خلفية المحادثة" _sfx: note: "الملاحظات" @@ -1357,6 +1365,14 @@ _widgets: userList: "قائمة المستخدمين" _userList: chooseList: "اختر قائمة" +_widgetOptions: + height: "الإرتفاع" + _button: + colored: "ملوّن" + _clock: + size: "الحجم" + _birthdayFollowings: + period: "المدة" _cw: hide: "إخفاء" show: "عرض المزيد" @@ -1397,6 +1413,9 @@ _postForm: replyPlaceholder: "رد على هذه الملاحظة…" quotePlaceholder: "اقتبس هذه الملاحظة…" channelPlaceholder: "انشر في قناة..." + _howToUse: + visibility_title: "الظهور" + menu_title: "القائمة" _placeholders: a: "ما الذي تنوي فعله؟" b: "ماذا يحدث حولك ؟" @@ -1592,3 +1611,21 @@ _search: searchScopeAll: "الكل" searchScopeLocal: "المحلي" searchScopeUser: "مستخدم محدد" +_watermarkEditor: + opacity: "الشفافية" + scale: "الحجم" + text: "نص" + position: "الموضع" + type: "نوع" + image: "صور" + advanced: "متقدم" +_imageEffector: + _fxProps: + scale: "الحجم" + size: "الحجم" + offset: "الموضع" + color: "اللون" + opacity: "الشفافية" +_qr: + showTabTitle: "المظهر" + raw: "نص" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 442fed6e21..9af4fc0ec4 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -215,7 +215,6 @@ noUsers: "কোন ব্যাবহারকারী নেই" editProfile: "প্রোফাইল সম্পাদনা করুন" noteDeleteConfirm: "আপনি কি নোট ডিলিট করার ব্যাপারে নিশ্চিত?" pinLimitExceeded: "আপনি আর কোন নোট পিন করতে পারবেন না" -intro: "Misskey এর ইন্সটলেশন সম্পন্ন হয়েছে!দয়া করে অ্যাডমিন ইউজার তৈরি করুন।" done: "সম্পন্ন" processing: "প্রক্রিয়াধীন..." preview: "পূর্বরূপ দেখুন" @@ -673,7 +672,6 @@ experimentalFeatures: "পরীক্ষামূলক বৈশিষ্ট developer: "ডেভেলপার" makeExplorable: "অ্যাকাউন্ট \"ঘুরে দেখুন\" পৃষ্ঠায় দেখান" makeExplorableDescription: "আপনি এটি বন্ধ করলে, আপনার অ্যাকাউন্ট \"ঘুরে দেখুন\" পৃষ্ঠায় প্রদর্শিত হবে না।" -showGapBetweenNotesInTimeline: "টাইমলাইন এবং নোটের মাঝে ফাকা জায়গা রাখুন" duplicate: "প্রতিরূপ" left: "বাম" center: "মাঝখান" @@ -850,6 +848,17 @@ sourceCode: "সোর্স কোড" flip: "উল্টান" postForm: "নোট লিখুন" information: "আপনার সম্পর্কে" +inMinutes: "মিনিট" +inDays: "দিন" +widgets: "উইজেটগুলি" +_imageEditing: + _vars: + filename: "ফাইলের নাম" +_imageFrameEditor: + header: "হেডার" + font: "ফন্ট" + fontSerif: "সেরিফ" + fontSansSerif: "স্যান্স সেরিফ" _chat: invitations: "আমন্ত্রণ" noHistory: "কোনো ইতিহাস নেই" @@ -1019,7 +1028,6 @@ _theme: buttonBg: "বাটনের পটভূমি" buttonHoverBg: "বাটনের পটভূমি (হভার)" inputBorder: "ইনপুট ফিল্ডের বর্ডার" - driveFolderBg: "ড্রাইভ ফোল্ডারের পটভূমি" badge: "ব্যাজ" messageBg: "চ্যাটের পটভূমি" fgHighlighted: "হাইলাইট করা পাঠ্য" @@ -1129,6 +1137,14 @@ _widgets: aichan: "আই চান" _userList: chooseList: "লিস্ট নির্বাচন করুন" +_widgetOptions: + height: "উচ্চতা" + _button: + colored: "রঙ্গিন" + _clock: + size: "আকার" + _birthdayFollowings: + period: "ব্যাপ্তিকাল" _cw: hide: "লুকান" show: "আরও দেখুন" @@ -1169,6 +1185,9 @@ _postForm: replyPlaceholder: "নোটটির জবাব দিন..." quotePlaceholder: "নোটটিকে উদ্ধৃত করুন..." channelPlaceholder: "চ্যানেলে পোস্ট করুন..." + _howToUse: + visibility_title: "দৃশ্যমানতা" + menu_title: "মেনু" _placeholders: a: "আপনি এখন কি করছেন?" b: "আপনার আশে পাশে কি হচ্ছে?" @@ -1352,3 +1371,19 @@ _remoteLookupErrors: _search: searchScopeAll: "সবগুলো" searchScopeLocal: "স্থানীয়" +_watermarkEditor: + opacity: "অস্বচ্ছতা" + scale: "আকার" + text: "লেখা" + image: "ছবি" + advanced: "উন্নত" +_imageEffector: + _fxProps: + scale: "আকার" + size: "আকার" + color: "রং" + opacity: "অস্বচ্ছতা" + lightness: "উজ্জ্বল করুন" +_qr: + showTabTitle: "প্রদর্শন" + raw: "লেখা" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 129c3d24a5..edaa0647ad 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -83,6 +83,8 @@ files: "Fitxers" download: "Descarregar" driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades." unfollowConfirm: "Segur que vols deixar de seguir a {name}?" +cancelFollowRequestConfirm: "Vols cancel·lar la teva sol·licitud de seguiment a {name}?" +rejectFollowRequestConfirm: "Vols rebutjar la sol·licitud de seguiment de {name}?" exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada." importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona." lists: "Llistes" @@ -220,6 +222,7 @@ silenceThisInstance: "Silencia aquesta instància " mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància " operations: "Accions" software: "Programari" +softwareName: "Nom del programari" version: "Versió" metadata: "Metadades" withNFiles: "{n} fitxer(s)" @@ -250,9 +253,9 @@ noUsers: "No hi ha usuaris" editProfile: "Edita el perfil" noteDeleteConfirm: "Segur que voleu eliminar aquesta publicació?" pinLimitExceeded: "No podeu fixar més publicacions" -intro: "La instal·lació de Misskey ha acabat! Crea un usuari d'administrador." done: "Fet" processing: "S'està processant..." +preprocessing: "Preparant" preview: "Vista prèvia" default: "Per defecte" defaultValueIs: "Per defecte: {value}" @@ -280,7 +283,7 @@ featured: "Destacat" usernameOrUserId: "Nom o ID d'usuari" noSuchUser: "No s'ha trobat l'usuari" lookup: "Cerca" -announcements: "Anuncis" +announcements: "Avisos" imageUrl: "URL de la imatge" remove: "Eliminar" removed: "Eliminat" @@ -298,8 +301,10 @@ uploadFromUrl: "Carrega des d'un enllaç" uploadFromUrlDescription: "Enllaç del fitxer que vols carregar" uploadFromUrlRequested: "Càrrega sol·licitada" uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot trigar un temps" +uploadNFiles: "Pujar {n} arxius" explore: "Explora" messageRead: "Vist" +readAllChatMessages: "Marcar tots els missatges com a llegits" noMoreHistory: "No hi ha res més per veure" startChat: "Comença a xatejar " nUsersRead: "Vist per {n}" @@ -326,11 +331,13 @@ dark: "Fosc" lightThemes: "Temes clars" darkThemes: "Temes foscos" syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" es troba activat. Vols desactivar la sincronització i canviar de mode manualment?" drive: "Disc" fileName: "Nom del Fitxer" selectFile: "Selecciona un fitxer" selectFiles: "Selecciona fitxers" selectFolder: "Selecció de carpeta" +unselectFolder: "Deixa de seleccionar la carpeta" selectFolders: "Selecció de carpetes" fileNotSelected: "Cap fitxer seleccionat" renameFile: "Canvia el nom del fitxer" @@ -343,6 +350,7 @@ addFile: "Afegeix un fitxer" showFile: "Mostrar fitxer" emptyDrive: "El teu Disc és buit" emptyFolder: "La carpeta està buida" +dropHereToUpload: "Arrossega els arxius fins aquí per pujar-los al servidor" unableToDelete: "No es pot eliminar" inputNewFileName: "Introduïu el nom de fitxer nou" inputNewDescription: "Escriu el peu de foto." @@ -575,8 +583,10 @@ showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de showFixedPostFormInChannel: "Mostrar el formulari d'escriptura al principi de la línia de temps (Canals)" withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous que segueixes a la línia de temps per defecte." newNoteRecived: "Hi ha publicacions noves" +newNote: "Notes noves" sounds: "Sons" sound: "So" +notificationSoundSettings: "Configuració del so de notificació" listen: "Escoltar" none: "Res" showInPage: "Mostrar a la pàgina " @@ -644,7 +654,7 @@ disablePlayer: "Tanca el reproductor de vídeo" expandTweet: "Expandir post" themeEditor: "Editor de temes" description: "Descripció" -describeFile: "Afegeix una descripció " +describeFile: "Afegir text alternatiu" enterFileDescription: "Escriu un peu de foto" author: "Autor" leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?" @@ -768,6 +778,7 @@ lockedAccountInfo: "Tret que establiu la visibilitat de la nota a \"Només segui alwaysMarkSensitive: "Marcar com a sensible per defecte" loadRawImages: "Carregar les imatges originals en comptes de miniatures " disableShowingAnimatedImages: "No reproduir imatges animades" +disableShowingAnimatedImages_caption: "Si les imatges animades no es reprodueixen, independentment d'aquesta configuració, és possible que la configuració d'accessibilitat del navegador i el sistema operatiu, els modes d'estalvi d'energia i similars estiguin interferint." highlightSensitiveMedia: "Ressalta els medis marcats com a sensibles" verificationEmailSent: "S'ha enviat un correu electrònic de verificació. Fes clic a l'enllaç per completar la verificació." notSet: "Sense definir" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "Aquesta és una característica experimental. La sev developer: "Programador" makeExplorable: "Fes que el compte sigui visible a la secció \"Explorar\"" makeExplorableDescription: "Si desactives aquesta opció, el teu compte no sortirà a la secció \"Explorar\"" -showGapBetweenNotesInTimeline: "Notes separades a la línia de temps" duplicate: "Duplicat" left: "Esquerra" center: "Centre" @@ -792,6 +802,7 @@ wide: "Gran" narrow: "Estret" reloadToApplySetting: "Aquest ajust només s'aplicarà després de recarregar la pàgina. Vols fer-ho ara?" needReloadToApply: "Es requereix recarregar per reflectir aquesta opció " +needToRestartServerToApply: "És necessari reiniciar el servidor perquè tinguin efecte els canvis." showTitlebar: "Mostra la barra del títol " clearCache: "Esborra la memòria cau" onlineUsersCount: "{n} Usuaris es troben en línia " @@ -871,7 +882,7 @@ gallery: "Galeria" recentPosts: "Articles recents" popularPosts: "Articles populars" shareWithNote: "Comparteix amb una nota" -ads: "Anuncis" +ads: "Publicitat " expiration: "" startingperiod: "Inici" memo: "Recordatori" @@ -892,7 +903,7 @@ searchResult: "Resultats de la cerca" hashtags: "Etiquetes" troubleshooting: "Solucionar problemes" useBlurEffect: "Fes servir efectes de desenfocament a la interfície" -learnMore: "Saber més " +learnMore: "Saber-ne més " misskeyUpdated: "Misskey s'ha actualitzat " whatIsNew: "Mostra canvis" translate: "Traduir " @@ -998,6 +1009,7 @@ failedToUpload: "Ha fallat la pujada" cannotUploadBecauseInappropriate: "Aquest fitxer no es pot pujar perquè s'ha trobat que algunes parts són inapropiades." cannotUploadBecauseNoFreeSpace: "Ha fallat la pujada del fitxer perquè no hi ha capacitat al Disc." cannotUploadBecauseExceedsFileSizeLimit: "Aquest fitxer no es pot pujar perquè supera la mida permesa." +cannotUploadBecauseUnallowedFileType: "Impossible pujar l'arxiu no és un tipus de fitxer autoritzat." beta: "Proves" enableAutoSensitive: "Marcar com a sensible automàticament " enableAutoSensitiveDescription: "Permet la detecció i el marcat automàtic dels mitjans sensibles fent servir aprenentatge automàtic quan sigui possible. Si aquesta opció es troba desactivada potser que estigui activada per a tota la instància. " @@ -1013,6 +1025,9 @@ pushNotificationAlreadySubscribed: "L'enviament de notificacions ja és activat" pushNotificationNotSupported: "El teu navegador o la teva instància no suporta l'enviament de notificacions " sendPushNotificationReadMessage: "Esborrar les notificacions enviades quan s'hagin llegit" sendPushNotificationReadMessageCaption: "Això pot fer que el teu dispositiu consumeixi més bateria" +pleaseAllowPushNotification: "Si us plau, permet les notificacions del navegador" +browserPushNotificationDisabled: "No s'ha pogut obtenir permisos per les notificacions" +browserPushNotificationDisabledDescription: "No tens permisos per enviar notificacions des de {serverName}. Activa les notificacions a la configuració del teu navegador i tornar-ho a intentar." windowMaximize: "Maximitzar " windowMinimize: "Minimitzar" windowRestore: "Restaurar" @@ -1049,6 +1064,7 @@ permissionDeniedError: "Operació no permesa " permissionDeniedErrorDescription: "Aquest compte no té suficients permisos per dur a terme aquesta acció " preset: "Predefinit" selectFromPresets: "Escull des dels predefinits" +custom: "Personalitzat" achievements: "Assoliments" gotInvalidResponseError: "Resposta del servidor invàlida " gotInvalidResponseErrorDescription: "No es pot contactar amb el servidor o potser es troba fora de línia per manteniment. Provar-ho de nou més tard." @@ -1087,6 +1103,7 @@ prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'ex hiddenTags: "Etiquetes ocultes" hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." notesSearchNotAvailable: "La cerca de notes no es troba disponible." +usersSearchNotAvailable: "La cerca d'usuaris no està disponible." license: "Llicència" unfavoriteConfirm: "Esborrar dels favorits?" myClips: "Els meus retalls" @@ -1110,7 +1127,7 @@ accountMigration: "Migració del compte" accountMoved: "Aquest usuari té un compte nou:" accountMovedShort: "Aquest compte ha sigut migrat" operationForbidden: "Operació no permesa " -forceShowAds: "Mostra els anuncis sempre " +forceShowAds: "Mostrar publicitat sempre " addMemo: "Afegir recordatori" editMemo: "Editar recordatori" reactionsList: "Reaccions" @@ -1161,6 +1178,7 @@ installed: "Instal·lats " branding: "Marca" enableServerMachineStats: "Publicar estadístiques del maquinari del servidor" enableIdenticonGeneration: "Activar la generació d'icones d'identificació " +showRoleBadgesOfRemoteUsers: "Mostrar insígnies de rols d'instàncies remotes " turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment." createInviteCode: "Crear codi d'invitació " createWithOptions: "Crear invitació amb opcions" @@ -1185,8 +1203,8 @@ iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord." dialog: "Diàleg " icon: "Icona" forYou: "Per a tu" -currentAnnouncements: "Informes actuals" -pastAnnouncements: "Informes passats" +currentAnnouncements: "Avisos actuals" +pastAnnouncements: "Avisos passats" youHaveUnreadAnnouncements: "Tens informes per llegir." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." replies: "Respostes" @@ -1237,9 +1255,8 @@ showAvatarDecorations: "Mostrar les decoracions dels avatars" releaseToRefresh: "Deixar anar per actualitzar" refreshing: "Recarregant..." pullDownToRefresh: "Llisca cap a baix per recarregar" -disableStreamingTimeline: "Desactivar l'actualització en temps real de les línies de temps" useGroupedNotifications: "Mostrar les notificacions agrupades " -signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat." +emailVerificationFailedError: "Hem tingut un problema en verificar la teva adreça de correu electrònic. És probable que l'enllaç estigui caducat." cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció " doReaction: "Afegeix una reacció " code: "Codi" @@ -1309,6 +1326,8 @@ availableRoles: "Roles disponibles " acknowledgeNotesAndEnable: "Activa'l després de comprendre els possibles perills." federationSpecified: "Aquest servidor treballa amb una federació de llistes blanques. No pot interactuar amb altres servidors que no siguin els especificats per l'administrador." federationDisabled: "La unió es troba deshabilitada en aquest servidor. No es pot interactuar amb usuaris d'altres servidors." +draft: "Esborrany " +draftsAndScheduledNotes: "Esborranys i publicacions programades" confirmOnReact: "Confirmar en reaccionar" reactAreYouSure: "Vols reaccionar amb \"{emoji}\"?" markAsSensitiveConfirm: "Vols marcar aquest contingut com a sensible?" @@ -1326,6 +1345,7 @@ restore: "Restaurar " syncBetweenDevices: "Sincronització entre dispositius" preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" +preferenceSyncConflictChoiceMerge: "Integració " preferenceSyncConflictChoiceServer: "Valors de configuració del servidor" preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu " preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització " @@ -1335,6 +1355,8 @@ postForm: "Formulari de publicació" textCount: "Nombre de caràcters " information: "Informació" chat: "Xat" +directMessage: "Xateja amb aquest usuari" +directMessage_short: "Missatge" migrateOldSettings: "Migrar la configuració anterior" migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual." compress: "Comprimir " @@ -1345,7 +1367,100 @@ embed: "Incrustar" settingsMigrating: "Estem migrant la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard, manualment, anant a Preferències → Altres → Migrar configuració antiga)" readonly: "Només lectura" goToDeck: "Tornar al tauler" +federationJobs: "Treballs de federació" +driveAboutTip: "Al Disc veure's una llista de tots els arxius que has anat pujant.
\nPots tornar-los a fer servir adjuntant-los a notes noves o pots adelantar-te i pujar arxius per publicar-los més tard!
\nTingués en compte que si esborres un arxiu també desapareixerà de tots els llocs on l'has fet servir (notes, pàgines, avatars, imatges de capçalera, etc.)
\nTambé pots crear carpetes per organitzar les." +scrollToClose: "Desplaçar per tancar" +advice: "Consell" +realtimeMode: "Mode en temps real" +turnItOn: "Activar" +turnItOff: "Desactivar" +emojiMute: "Silenciar emojis" +emojiUnmute: "Deixar de silenciar emojis" +muteX: "Silenciar {x}" +unmuteX: "Deixar de silenciar {x}" +abort: "Cancel·lar" +tip: "Trucs i consells" +redisplayAllTips: "Torna ha mostrat tots els trucs i consells" +hideAllTips: "Amagar tots els trucs i consells" +defaultImageCompressionLevel: "Nivell de comprensió de la imatge per defecte" +defaultImageCompressionLevel_description: "Baixa, conserva la qualitat de la imatge però la mida de l'arxiu és més gran.
Alta, redueix la mida de l'arxiu però també la qualitat de la imatge." +defaultCompressionLevel: "Nivell de compressió predeterminat" +defaultCompressionLevel_description: "Si el redueixes augmentaràs la qualitat de la imatge, però la mida de l'arxiu serà més gran.
Si augmentes l'opció redueixes la mida de l'arxiu i la qualitat de la imatge és pitjor." +inMinutes: "Minut(s)" +inDays: "Di(a)(es)" +safeModeEnabled: "Mode segur activat" +pluginsAreDisabledBecauseSafeMode: "Els afegits no estan activats perquè el mode segur està activat." +customCssIsDisabledBecauseSafeMode: "El CSS personalitzat no s'aplica perquè el mode segur es troba activat." +themeIsDefaultBecauseSafeMode: "El tema predeterminat es farà servir mentre el mode segur estigui activat. Una vegada es desactivi el mode segur es restablirà el tema escollit." +thankYouForTestingBeta: "Gràcies per ajudar-nos a provar la versió beta!" +createUserSpecifiedNote: "Crear notes especificades per l'usuari " +schedulePost: "Programar una nota" +scheduleToPostOnX: "Programar una nota per {x}" +scheduledToPostOnX: "S'ha programat la nota per {x}" +schedule: "Programa" +scheduled: "Programat" +widgets: "Ginys" +deviceInfo: "Informació del dispositiu" +deviceInfoDescription: "En fer consultes tècniques influir la següent informació pot ajudar a resoldre'l més ràpidament." +youAreAdmin: "Ets l'administrador " +frame: "Marc" +presets: "Predefinit" +zeroPadding: "Sense omplir" +nothingToConfigure: "No hi ha res a configurar" +_imageEditing: + _vars: + caption: "Títol de l'arxiu" + filename: "Nom del Fitxer" + filename_without_ext: "Nom de l'arxiu sense extensió " + year: "Any" + month: "Mes" + day: "Dia" + hour: "Hora" + minute: "Minut" + second: "Segon" + camera_model: "Nom de la càmera " + camera_lens_model: "Nom de la lent" + camera_mm: "Distància focal" + camera_mm_35: "Distància focal (equivalent a 35 mm)" + camera_f: "Obertura" + camera_s: "Velocitat d'obturació" + camera_iso: "Sensibilitat ISO" + gps_lat: "Latitud " + gps_long: "Longitud " +_imageFrameEditor: + title: "Edició de fotogrames " + tip: "Pots decorar les imatges afegint etiquetes que continguin marcs i metadades." + header: "Capçalera" + footer: "Peu de pàgina " + borderThickness: "Amplada de la vora" + labelThickness: "Amplada de l'etiqueta " + labelScale: "Mida de l'etiqueta " + centered: "Alinea al centre" + captionMain: "Peu de foto (gran)" + captionSub: "Peu de foto (petit)" + availableVariables: "Variables disponibles" + withQrCode: "Codi QR" + backgroundColor: "Color del fons" + textColor: "Color del text" + font: "Lletra tipogràfica" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "Sortir sense desar?" + failedToLoadImage: "Error en carregar la imatge" +_compression: + _quality: + high: "Qualitat alta" + medium: "Qualitat mitjana" + low: "Qualitat baixa" + _size: + large: "Mida gran" + medium: "Mida mitjana" + small: "Mida petita" +_order: + newest: "Més recent" + oldest: "Antigues primer" _chat: + messages: "Missatge" noMessagesYet: "Encara no tens missatges " newMessage: "Missatge nou" individualChat: "Xat individual " @@ -1378,6 +1493,8 @@ _chat: chatNotAvailableInOtherAccount: "La funció de xat es troba desactivada al compte de l'altre usuari." cannotChatWithTheUser: "No pots xatejar amb aquest usuari" cannotChatWithTheUser_description: "El xat està desactivat o l'altra part encara no l'ha obert." + youAreNotAMemberOfThisRoomButInvited: "No participes en aquesta sala, però has rebut una invitació. Per participar accepta la invitació." + doYouAcceptInvitation: "Acceptes la invitació?" chatWithThisUser: "Xateja amb aquest usuari" thisUserAllowsChatOnlyFromFollowers: "Aquest usuari només accepta xats d'usuaris que el segueixen." thisUserAllowsChatOnlyFromFollowing: "Aquest usuari només accepta xats d'usuaris que segueix." @@ -1417,10 +1534,26 @@ _settings: makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables" makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions." useStickyIcons: "Utilitza icones fixes" + enableHighQualityImagePlaceholders: "Mostrar marcadors de posició per imatges d'alta qualitat" + uiAnimations: "Animacions de la interfície" showNavbarSubButtons: "Mostrar sub botons a la barra de navegació " ifOn: "Quan s'activa" ifOff: "Quan es desactiva" enableSyncThemesBetweenDevices: "Sincronitzar els temes instal·lats entre dispositius" + enablePullToRefresh: "Lliscar i actualitzar " + enablePullToRefresh_description: "Amb el ratolí, llisca mentre prems la roda." + realtimeMode_description: "Estableix una connexió amb el servidor i actualitza el contingut en temps real. Pot consumir més dades i bateria." + contentsUpdateFrequency: "Freqüència d'adquisició del contingut" + contentsUpdateFrequency_description: "Com més alt sigui l'adquisició de contingut en temps real, més baixa el rendiment i més consum de dades i bateria." + contentsUpdateFrequency_description2: "Quan s'activa el mode en temps real, el contingut s'actualitza en temps real, independentment d'aquesta configuració." + showUrlPreview: "Mostrar vista prèvia d'URL" + showAvailableReactionsFirstInNote: "Mostra les reacciones que pots fer servir al damunt" + showPageTabBarBottom: "Mostrar les pestanyes de les línies de temps a la part inferior" + emojiPaletteBanner: "Pots registrar ajustos preestablerts com paletes perquè es mostrin permanentment al selector d'emojis, o personalitzar la configuració de visió del selector." + enableAnimatedImages: "Activar imatges animades" + settingsPersistence_title: "Persistència de la configuració " + settingsPersistence_description1: "Habilitar la persistència de la configuració permet que no es perdi la informació de la configuració " + settingsPersistence_description2: "Depenent de l'entorn pot ser que no puguis habilitar aquesta opció." _chat: showSenderName: "Mostrar el nom del remitent" sendOnEnter: "Introdueix per enviar" @@ -1428,6 +1561,9 @@ _preferencesProfile: profileName: "Nom del perfil" profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." profileNameDescription2: "Per exemple: \"PC Principal\", \"Smartphone\", etc" + manageProfiles: "Gestionar perfils" + shareSameProfileBetweenDevicesIsNotRecommended: "No recomanem compartir el mateix perfil en diferents dispositius." + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "Si hi ha ajustos que vols sincronitzar entre diferents dispositius activa l'opció \"Sincronitza entre diferents dispositius\" individualment per cada una de les diferents opcions." _preferencesBackup: autoBackup: "Còpia de seguretat automàtica " restoreFromBackup: "Restaurar des d'una còpia de seguretat" @@ -1437,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "Has de posar-li un nom al teu perfil per poder activar les còpies de seguretat automàtiques." autoPreferencesBackupIsNotEnabledForThisDevice: "La còpia de seguretat automàtica no es troba activada en aquest dispositiu." backupFound: "Còpia de seguretat de la configuració trobada" + forceBackup: "Còpia de seguretat forçada de la configuració " _accountSettings: requireSigninToViewContents: "És obligatori l'inici de sessió per poder veure el contingut" requireSigninToViewContentsDescription1: "Es requereix l'inici de sessió per poder veure totes les notes i el contingut que has creat. Amb això esperem evitar que els rastrejadors recopilin informació." @@ -1466,6 +1603,7 @@ _delivery: manuallySuspended: "Suspendre manualment" goneSuspended: "Servidor suspès perquè el servidor s'ha esborrat" autoSuspendedForNotResponding: "Servidor suspès perquè el servidor no respon" + softwareSuspended: "Suspès perquè el programari ha deixat de desenvolupar-se " _bubbleGame: howToPlay: "Com es juga" hold: "Mantenir" @@ -1475,8 +1613,8 @@ _bubbleGame: highScore: "Millor puntuació " maxChain: "Nombre màxim de combos" yen: "{yen}Ien" - estimatedQty: "{qty}peces" - scoreSweets: "{onigiriQtyWithUnit}ongiris" + estimatedQty: "{qty} Peces" + scoreSweets: "{onigiriQtyWithUnit} Boles d'arròs " _howToPlay: section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa." section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts." @@ -1487,7 +1625,7 @@ _announcement: needConfirmationToRead: "Es necessita confirmació de lectura de la notificació " needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació. A més aquesta notificació serà exclosa de qualsevol funcionalitat com \"Marcar tot com a llegit\"." end: "Final de la notificació " - tooManyActiveAnnouncementDescription: "Tenir massa notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els anuncis que siguin antics." + tooManyActiveAnnouncementDescription: "Tenir masses notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els avisos que siguin antics." readConfirmTitle: "Marcar com llegida?" readConfirmText: "Això marcarà el contingut de \"{title}\" com llegit." shouldNotBeUsedToPresentPermanentInfo: "Ja que l'ús de notificacions pot impactar l'experiència dels nous usuaris, és recomanable fer servir les notificacions amb el flux d'informació en comptes de fer-les servir en un únic bloc." @@ -1592,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "Carregar de la base de dades" fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir." reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." + remoteNotesCleaning: "Neteja automàtica de notes remotes" + remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se" + remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja" + remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes" inquiryUrl: "URL de consulta " inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." openRegistration: "Registres oberts" openRegistrationWarning: "Obrir els registres és arriscat. Es recomana obrir-los només si el servidor és monitorat constantment i per respondre immediatament davant qualsevol problema." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Si no es detecta activitat per part del moderador durant un període de temps, aquesta opció es desactiva automàticament per evitar el correu brossa." + deliverSuspendedSoftware: "Programari que ja no es distribueix" + deliverSuspendedSoftwareDescription: "Pots especificar un rang de noms i versions del programari del servidor per detenir l'entrega, per exemple, degut a vulnerabilitats. Aquesta informació la proporciona el servidor i la seva fiabilitat no es garantitzada. Es pot fer servir una especificació de rang sencer per especificar una versió, però es recomana especificar una versió anterior, com >= 2024.3.1-0, perquè especificar >= 2024.3.1 no incloure versions personalitzades com 2024.3.1-custom.0." + singleUserMode: "Mode un usuari" + singleUserMode_description: "Si ets l'únic usuari d'aquesta instància, activant aquest mode optimitzaràs el funcionament." + signToActivityPubGet: "Formar sol·licituds GET" + signToActivityPubGet_description: " Això normalment hauria d'estar activat. Desactivar aquesta opció pot millorar els problemes de comunicació amb algunes de les instàncies federades, però també pot fer impossibles les comunicacions amb altres servidors." + proxyRemoteFiles: "Proxy d'arxius remots" + proxyRemoteFiles_description: "Quan està habilitat, fa de proxy i serveix arxius remots. Això ajuda a generar les miniatures de les imatges i a protegir la privacitat dels usuaris." + allowExternalApRedirect: "Permetre el reencaminament per consultes fent servir ActivityPub." + allowExternalApRedirect_description: "Si aquesta opció s'activa, altres servidors poden consultar continguts de tercers mitjançant aquest servidor, però això pot donar peu a la suplantació de continguts." + userGeneratedContentsVisibilityForVisitor: "L'abast de la publicació del contingut generat per l'usuari" + userGeneratedContentsVisibilityForVisitor_description: "Això ajuda a evitar problemes com que continguts remots inadequats que no hagin estat moderats correctament es publiquin a internet mitjançant el teu servidor." + userGeneratedContentsVisibilityForVisitor_description2: "La publicació incondicional de tots els continguts del servidor a internet, incloent-hi els continguts remots rebuts pel servidor, comporta riscos. Això és extremadament important per els espectadors que desconeixen el caràcter descentralitzat dels continguts, ja que poden percebre erroneament els continguts remots com contingut generat per el propi servidor." + restartServerSetupWizardConfirm_title: "Vols tornar a executar l'assistent de configuració inicial del servidor?" + restartServerSetupWizardConfirm_text: "Algunes configuracions actuals seran restablertes." + entrancePageStyle: "Estil de la pàgina d'inici" + showTimelineForVisitor: "Mostrar la línia de temps" + showActivitiesForVisitor: "Mostrar activitat" + _userGeneratedContentsVisibilityForVisitor: + all: "Tot obert al públic " + localOnly: "Només es publiquen els continguts locals, el contingut remot es manté privat" + none: "Tot privat" _accountMigration: moveFrom: "Migrar un altre compte a aquest" moveFromSub: "Crear un àlies per un altre compte" @@ -1914,6 +2078,8 @@ _role: canManageCustomEmojis: "Gestiona els emojis personalitzats" canManageAvatarDecorations: "Gestiona les decoracions dels avatars " driveCapacity: "Capacitat del disc" + maxFileSize: "Mida màxima de l'arxiu que es pot carregar" + maxFileSize_caption: "Pot haver-hi la possibilitat que existeixin altres opcions de configuració de l'etapa anterior, com podria ser el proxy invers i la CDN." alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles" canUpdateBioMedia: "Permet l'edició d'una icona o un bàner" pinMax: "Nombre màxim de notes fixades" @@ -1926,8 +2092,9 @@ _role: userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris " rateLimitFactor: "Limitador" descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius." - canHideAds: "Pot amagar els anuncis" + canHideAds: "Pot amagar la publicitat" canSearchNotes: "Pot cercar notes" + canSearchUsers: "Pot cercar usuaris" canUseTranslator: "Pot fer servir el traductor" avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars" canImportAntennas: "Autoritza la importació d'antenes " @@ -1936,6 +2103,12 @@ _role: canImportMuting: "Autoritza la importació de silenciats" canImportUserLists: "Autoritza la importació de llistes d'usuaris " chatAvailability: "Es permet xatejar" + uploadableFileTypes: "Tipus de fitxers que en podeu pujar" + uploadableFileTypes_caption: "Especifica el tipus MIME. Es poden especificar diferents tipus MIME separats amb una nova línia, i es poden especificar comodins amb asteriscs (*). (Per exemple: image/*)" + uploadableFileTypes_caption2: "Pot que no sigui possible determinar el tipus MIME d'alguns arxius. Per permetre aquests tipus d'arxius afegeix {x} a les especificacions." + noteDraftLimit: "Nombre possible d'esborranys de notes al servidor" + scheduledNoteLimit: "Màxim nombre de notes programades que es poden crear simultàniament" + watermarkAvailable: "Pots fer servir la marca d'aigua" _condition: roleAssignedTo: "Assignat a rols manuals" isLocal: "Usuari local" @@ -1991,9 +2164,9 @@ _ad: reduceFrequencyOfThisAd: "Mostrar menys aquest anunci" hide: "No mostrar mai" timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor." - adsSettings: "Configuració d'anuncis " - notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)" - setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real" + adsSettings: "Configurar la publicitat" + notesPerOneAd: "Interval d'emplaçament publicitari en temps real (Notes per anuncis)" + setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització de publicitat en temps real" adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix." _forgotPassword: enterEmail: "Escriu l'adreça de correu electrònic amb la que et vas registrar. S'enviarà un correu electrònic amb un enllaç perquè puguis canviar-la." @@ -2095,6 +2268,7 @@ _theme: install: "Instal·lar un tema" manage: "Gestionar els temes " code: "Codi del tema" + copyThemeCode: "Copiar el codi del tema" description: "Descripció" installed: "{name} Instal·lat " installedThemes: "Temes instal·lats " @@ -2153,7 +2327,6 @@ _theme: buttonBg: "Fons botó " buttonHoverBg: "Fons botó (en passar-hi per sobre)" inputBorder: "Contorn del cap d'introducció " - driveFolderBg: "Fons de la carpeta Disc" badge: "Insígnia " messageBg: "Fons del xat" fgHighlighted: "Text ressaltat" @@ -2195,6 +2368,7 @@ _time: minute: "Minut(s)" hour: "Hor(a)(es)" day: "Di(a)(es)" + month: "Mes(os)" _2fa: alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor." registerTOTP: "Registrar una aplicació autenticadora" @@ -2324,6 +2498,7 @@ _auth: scopeUser: "Opera com si fossis aquest usuari" pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació." byClickingYouWillBeRedirectedToThisUrl: "Si es garanteix l'accés, seràs redirigit automàticament a la següent adreça URL" + alreadyAuthorized: "Aquesta aplicació ja té accés." _antennaSources: all: "Totes les publicacions" homeTimeline: "Publicacions dels usuaris seguits" @@ -2369,7 +2544,45 @@ _widgets: chooseList: "Tria una llista" clicker: "Clicker" birthdayFollowings: "Usuaris que fan l'aniversari avui" - chat: "Xat" + chat: "Xateja amb aquest usuari" +_widgetOptions: + showHeader: "Mostrar la capçalera" + transparent: "Fons transparent" + height: "Alçada " + _button: + colored: "Colorit" + _clock: + size: "Mida" + thickness: "Amplada de l'agulla " + thicknessThin: "Esvelt " + thicknessMedium: "Normal" + thicknessThick: "Gruixut " + graduations: "Marques de l'esfera " + graduationDots: "Punt" + graduationArabic: "Nombres àrabs " + fadeGraduations: "Efecte gradient " + sAnimation: "Animació de la maneta dels segons" + sAnimationElastic: "Real" + sAnimationEaseOut: "Suau" + twentyFour: "Format 24 hores" + labelTime: "Temps" + labelTz: "Fus horari" + labelTimeAndTz: "Hora i fus horari" + timezone: "Fus horari" + showMs: "Mostrar mil·lisegons" + showLabel: "Mostrar etiqueta" + _jobQueue: + sound: "Reprodueix so" + _rss: + url: "URL del canal RSS" + refreshIntervalSec: "Interval d'actualitzacions (segons)" + maxEntries: "Nombre màxim d'entrades a mostrar" + _rssTicker: + shuffle: "Visualització aleatòria " + duration: "Velocitat desplaçament bàner informatiu " + reverse: "Desplaçament contrari" + _birthdayFollowings: + period: "Període" _cw: hide: "Amagar" show: "Carregar més" @@ -2409,9 +2622,25 @@ _visibility: disableFederation: "Sense federar" disableFederationDescription: "No enviar a altres servidors" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Hi ha arxius que no s'han carregat, vols descartar-los i tancar el formulari?" + uploaderTip: "L'arxiu encara no s'ha carregat. Des del menú arxiu pots canviar el nom, retallar imatges, posar marques d'aigua i comprimir o no l'arxiu. Els arxius es carreguen automàticament quan públiques una nota." replyPlaceholder: "Contestar..." quotePlaceholder: "Citar..." channelPlaceholder: "Publicar a un canal..." + showHowToUse: "Mostrar les instruccions" + _howToUse: + content_title: "Cos principal" + content_description: "Introdueix el contingut que vols publicar." + toolbar_title: "Barra d'eines " + toolbar_description: "Pots adjuntar arxius o enquestes, afegir anotacions o etiquetes i inserir emojis o mencions." + account_title: "Menú del compte" + account_description: "Pots anar canviant de comptes per publicar o veure una llista d'esborranys i les publicacions programades del teu compte." + visibility_title: "Visibilitat" + visibility_description: "Pots configurar la visibilitat de les teves notes." + menu_title: "Menú" + menu_description: "Pots fer altres accions com desar esborranys, programar publicacions i configurar reaccions." + submit_title: "Botó per publicar" + submit_description: "Publica les teves notes. També pots fer servir Ctrl + Enter / Cmd + Enter" _placeholders: a: "Que vols dir?..." b: "Alguna cosa interessant al teu voltant?..." @@ -2557,6 +2786,8 @@ _notification: youReceivedFollowRequest: "Has rebut una petició de seguiment" yourFollowRequestAccepted: "La teva petició de seguiment ha sigut acceptada" pollEnded: "Ja pots veure els resultats de l'enquesta " + scheduledNotePosted: "Una nota programada ha sigut publicada" + scheduledNotePostFailed: "Ha fallat la publicació d'una nota programada" newNote: "Nota nova" unreadAntennaNote: "Antena {name}" roleAssigned: "Rol assignat " @@ -2586,6 +2817,8 @@ _notification: quote: "Citar" reaction: "Reaccions" pollEnded: "Enquesta terminada" + scheduledNotePosted: "Nota programada amb èxit " + scheduledNotePostFailed: "Ha fallat la programació de la nota" receiveFollowRequest: "Rebuda una petició de seguiment" followRequestAccepted: "Petició de seguiment acceptada" roleAssigned: "Rol donat" @@ -2625,6 +2858,14 @@ _deck: usedAsMinWidthWhenFlexible: "L'amplada mínima es farà servir quan \"Ajust automàtic de l'amplada\" estigui activat" flexible: "Ajust automàtic de l'amplada" enableSyncBetweenDevicesForProfiles: "Activar la sincronització de la informació de perfils de dispositiu a dispositiu" + showHowToUse: "Veure la descripció de la interfície d'usuari " + _howToUse: + addColumn_title: "Afegir columna" + addColumn_description: "Pots seleccionar i afegir tipus de columnes." + settings_title: "Configuració de la interfície d'usuari " + settings_description: "Pots configurar la interfície d'usuari amb detall." + switchProfile_title: "Canviar perfil" + switchProfile_description: "Pots desar el disseny de la interfície d'usuari com un perfil i anar canviant entre ells quan vulguis." _columns: main: "Principal" widgets: "Ginys" @@ -2636,7 +2877,7 @@ _deck: mentions: "Mencions" direct: "Publicacions directes" roleTimeline: "Línia de temps dels rols" - chat: "Xat" + chat: "Xateja amb aquest usuari" _dialog: charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}" charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}" @@ -2685,6 +2926,8 @@ _abuseReport: notifiedWebhook: "Webhook que s'ha de fer servir" deleteConfirm: "Segur que vols esborrar el destinatari de l'informe de moderació?" _moderationLogTypes: + clearQueue: "Esborra la cua de feina" + promoteQueue: "Tornar a intentar la feina de la cua" createRole: "Rol creat" deleteRole: "Rol esborrat" updateRole: "Rol actualitzat" @@ -2742,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "Pujat el" attachedNotes: "Notes amb aquest fitxer" + usage: "Ús " thisPageCanBeSeenFromTheAuthor: "Aquesta pàgina només la pot veure l'usuari que ha pujat aquest fitxer." _externalResourceInstaller: title: "Instal·lar des d'un lloc extern" @@ -2789,9 +3033,12 @@ _dataSaver: _avatar: title: "Avatars animats" description: "Detenir l'animació dels avatars animats. Les imatges animades solen tenir un pes més gran que les imatges normals, reduint el tràfic disponible." - _urlPreview: - title: "Miniatures vista prèvia de l'URL" - description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar." + _urlPreviewThumbnail: + title: "Amagar les miniatures de la vista prèvia d'URL" + description: "Les imatges en miniatura de la vista prèvia d'URL ja no es carreguen" + _disableUrlPreview: + title: "Desactivar la vista prèvia d'URL" + description: "Desactiva la funció de previsualització d'URL. A diferència de les imatges en miniatura soles, això redueix la càrrega de la mateixa informació vinculada." _code: title: "Ressaltat del codi " description: "Quan s'utilitza codi MFM, no es llegeix fins que es copiï. En els punts destacats del codi s'han de llegir els fitxers definits per a cada llengua que resulti alt, però no es poden llegir automàticament, per la qual cosa es poden reduir les quantitats de comunicació." @@ -2849,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "Configuració per a la previsualització de l'URL" enable: "Activa la previsualització de l'URL" + allowRedirect: "Permet la redirecció de la visualització prèvia " + allowRedirectDescription: "Estableix si es mostra o no la redirecció a la vista prèvia quan l'adreça URL introduïda té una redirecció. Si es desactiva s'estalvien recursos del servidor, però no es mostrarà el contingut de la redirecció." timeout: "Temps màxim per carregar la previsualització de l'URL (ms)" timeoutDescription: "Si l'obtenció de la previsualització triga més que el temps establert, no es generarà la vista prèvia." maximumContentLength: "Longitud màxima del contingut (bytes)" @@ -2922,10 +3171,6 @@ _customEmojisManager: uploadSettingDescription: "En aquesta pantalla pots configurar el que s'ha de fer quan es puja un Emoji." directoryToCategoryLabel: "Escriu el nom del directori al camp de \"categoria\"" directoryToCategoryCaption: "Quan arrossegues un directori, escriu el nom del directori al camp categoria." - emojiInputAreaCaption: "Selecciona els Emojis que vols registrar gent servir un dels mètodes." - emojiInputAreaList1: "Arrossega i deixar anar fitxers o directoris dintre del quadrat." - emojiInputAreaList2: "Clica l'enllaç per seleccionar un fitxer des del teu ordinador." - emojiInputAreaList3: "Clica aquest enllaç per seleccionar del Disc" confirmRegisterEmojisDescription: "Registrar els Emojis de la llista com a nous Emojis personalitzats. Vols continuar? (Per evitar una sobrecàrrega només {count} Emojis es poden registrar d'una sola vegada)" confirmClearEmojisDescription: "Descartar els canvis i esborrar els Emojis de la llista. Vols continuar?" confirmUploadEmojisDescription: "Pujar els {count} fitxers que has arrossegat al disc. Vols continuar?" @@ -2993,6 +3238,7 @@ _bootErrors: otherOption1: "Esborrar la configuració i la memòria cau del client" otherOption2: "Iniciar client senzill" otherOption3: "Iniciar l'eina de reparació " + otherOption4: "Iniciar Misskey en mode segur" _search: searchScopeAll: "Tot" searchScopeLocal: "Local" @@ -3001,3 +3247,196 @@ _search: pleaseEnterServerHost: "Introdueix l'adreça de la instància " pleaseSelectUser: "Selecciona un usuari" serverHostPlaceholder: "Ex: misskey.example.com" +_serverSetupWizard: + installCompleted: "La instal·lació de Misskey ha finalitzat!" + firstCreateAccount: "Primer crea un compte d'administrador." + accountCreated: "Compte d'administrador creat." + serverSetting: "Configuració del servidor" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Aquest assistent t'ajuda a fer una configuració òptima del servidor." + settingsYouMakeHereCanBeChangedLater: "Els canvis que facis ara poden modificar-se més tard." + howWillYouUseMisskey: "Com es fa servir Misskey?" + _use: + single: "Servidor per una sola persona" + single_description: "Fes-ho servir com el teu propi servidor dedicat" + single_youCanCreateMultipleAccounts: "Es poden crear diferents comptes segons siguin les teves necessitats, inclús quan es fa servir com a servidor unipersonal." + group: "Servidor per a grups" + group_description: "Invita altres usuaris de la teva confiança i fes-ho servir amb més d'una persona." + open: "Servidor obert" + open_description: "Operar per donar cabuda a un nombre no determinat d'usuaris." + openServerAdvice: "Acceptar un nombre no determinat d'usuaris comporta alguns riscos. Es recomana operar amb un sistema de moderació fiable per fer front als problemes." + openServerAntiSpamAdvice: "També s'ha de tenir molta cura amb la seguretat, per exemple habilitant funcions anti-bot com reCAPTCHA, per assegurar-te que el teu servidor no es converteix en un trampolí per contingut brossa." + howManyUsersDoYouExpect: "Quantes persones preveus?" + _scale: + small: "Menys de 100 (petita escala)" + medium: "Més de 100 i menys de 1000 (mida mitjana)" + large: "Més de 1000 persones (gran escala)" + largeScaleServerAdvice: "Els grans servidors poden requerir coneixements avançats d'infraestructures, com balanceig de càrregues i replicació de base de dades." + doYouConnectToFediverse: "Desitges connectar-te amb el Fedivers?" + doYouConnectToFediverse_description1: "Quan es connecta amb una xarxa de servidors distribuïts (Fedivers), els continguts poden intercanviar-se amb altres servidors i entre ells." + doYouConnectToFediverse_description2: "La connexió amb el Fedivers també es coneix com a \"federació\"." + youCanConfigureMoreFederationSettingsLater: "Les configuracions avançades, com especificar els servidors amb els quals es pot federar, es poden fer més tard." + remoteContentsCleaning: "Neteja automàtica del contingut rebut" + remoteContentsCleaning_description: "Quan es comença a federar es rep un munt de contingut, quan s'activa la neteja automàtica el contingut antic que no es consulta serà eliminat del servidor, el que permet estalviar espai d'emmagatzematge." + adminInfo: "Informació de l'administrador " + adminInfo_description: "Estableix la informació de l'administrador que es farà servir per rebre consultes." + adminInfo_mustBeFilled: "Aquesta informació ha de ser omplerta si el servidor té els registres oberts o la federació es troba activada." + followingSettingsAreRecommended: "Es recomana la següent configuració " + applyTheseSettings: "Aplicar aquesta configuració " + skipSettings: "Saltar la configuració " + settingsCompleted: "Configuració finalitzada " + settingsCompleted_description: "Gràcies per la teva ajuda. Ara que ja està tot llest, pots començar a fer servir el servidor immediatament." + settingsCompleted_description2: "La configuració avançada del servidor també poden fer-se des del \"Tauler de control\"." + donationRequest: "Una donació, si us plau" + _donationRequest: + text1: "Misskey és un programari gratuït fet per voluntaris." + text2: "Si ho desitges, agrairíem molt la teva donació per poder seguir desenvolupant el projecte." + text3: "També hi ha privilegis especials per als donants!" +_uploader: + editImage: "Edició d'imatges" + compressedToX: "Comprimit a {x}" + savedXPercent: "{x}% d'estalvi " + abortConfirm: "Hi ha un arxiu que no s'ha pujat, vols cancel·lar?" + doneConfirm: "Hi han fitxers no pujats, vols completar-los?" + maxFileSizeIsX: "La mida màxima d'arxiu que es pot pujar és {x}." + allowedTypes: "Tipus de fitxers que en podeu pujar" + tip: "L'arxiu encara no s'ha carregat. En aquest quadre de diàleg, pots comprovar, canviar el nom, comprimir i retallar l'arxiu abans de pujar-lo. Quan estigui llest pots iniciar la càrrega polsant el boto \"Pujar\"" +_clientPerformanceIssueTip: + title: "Si creus que el consum de bateria és molt alt" + makeSureDisabledAdBlocker: "Desactiva els bloquejadors de publicitat" + makeSureDisabledAdBlocker_description: "Els bloquejadors d'anuncis pot afectar el rendiment, comprova que no estiguin activats per característiques del sistema operatiu o del navegador." + makeSureDisabledCustomCss: "Desactiva CSS personalitzat" + makeSureDisabledCustomCss_description: "L'anul·lació dels estils pot afectar el rendiment. Comprova que el CSS personalitzat o les extensions que reescriuen estils no estiguin activats." + makeSureDisabledAddons: "Desactiva extensions" + makeSureDisabledAddons_description: "Algunes extensions poden interferir en el comportament del client i afectar el rendiment. Desactiva les extensions del navegador i comprovar-ho." +_clip: + tip: "Clip és una funció que permet organitzar les teves notes." +_userLists: + tip: "Es poden crear llistes amb qualsevol usuari. La llista creada es pot mostrar com una línia de temps." +watermark: "Marca d'aigua " +defaultPreset: "Per defecte" +_watermarkEditor: + tip: "A la imatge es pot afegir una marca d'aigua com informació sobre drets." + quitWithoutSaveConfirm: "Sortir sense desar?" + driveFileTypeWarn: "Aquest arxiu no és compatible" + driveFileTypeWarnDescription: "Selecciona un arxiu d'imatge " + title: "Editar la marca d'aigua " + cover: "Cobrir-ho tot" + repeat: "Repetir" + preserveBoundingRect: "Ajusta'l per evitar que sobresortir en fer la rotació " + opacity: "Opacitat" + scale: "Mida" + text: "Text" + qr: "Codi QR" + position: "Posició " + margin: "Marge" + type: "Tipus" + image: "Imatges" + advanced: "Avançat" + angle: "Angle" + stripe: "Bandes" + stripeWidth: "Amplada de la banda" + stripeFrequency: "Freqüència de la banda" + polkadot: "Lunars" + checker: "Escacs" + polkadotMainDotOpacity: "Opacitat del lunar principal" + polkadotMainDotRadius: "Mida del lunar principal" + polkadotSubDotOpacity: "Opacitat del lunar secundari" + polkadotSubDotRadius: "Mida del lunar secundari" + polkadotSubDotDivisions: "Nombre de punts secundaris" + leaveBlankToAccountUrl: "Si deixes aquest camp buit, es farà servir l'URL del teu compte" + failedToLoadImage: "Error en carregar la imatge" +_imageEffector: + title: "Efecte" + addEffect: "Afegeix un efecte" + discardChangesConfirm: "Vols descartar els canvis i sortir?" + failedToLoadImage: "Error en carregar la imatge" + _fxs: + chromaticAberration: "Aberració cromàtica" + glitch: "Glitch" + mirror: "Mirall" + invert: "Inversió cromàtica " + grayscale: "Monocrom " + blur: "Desenfocament" + pixelate: "Mosaic" + colorAdjust: "Correcció de color" + colorClamp: "Compressió cromàtica " + colorClampAdvanced: "Compressió de cromàtica avançada " + distort: "Distorsió " + threshold: "Binarització" + zoomLines: "Saturació de línies " + stripe: "Bandes" + polkadot: "Lunars" + checker: "Escacs" + blockNoise: "Bloqueig de soroll" + tearing: "Trencament d'imatge " + fill: "Omplir" + _fxProps: + angle: "Angle" + scale: "Mida" + size: "Mida" + radius: "Radi" + samples: "Mida de la mostra" + offset: "Posició " + color: "Color" + opacity: "Opacitat" + normalize: "Normalitzar" + amount: "Quantitat" + lightness: "Brillantor" + contrast: "Contrast" + hue: "Tonalitat" + brightness: "Brillantor" + saturation: "Saturació" + max: "Màxim" + min: "Mínim" + direction: "Direcció " + phase: "Fase" + frequency: "Freqüència " + strength: "Intensitat" + glitchChannelShift: "Canvi de canal " + seed: "Llavors" + redComponent: "Component vermell" + greenComponent: "Component verd" + blueComponent: "Component blau" + threshold: "Llindar" + centerX: "Centre de X" + centerY: "Centre de Y" + zoomLinesSmoothing: "Suavitzat" + zoomLinesSmoothingDescription: "Els paràmetres de suavitzat i amplada de línia en augmentar no es poden fer servir junts." + zoomLinesThreshold: "Amplada de línia a l'augmentar " + zoomLinesMaskSize: "Diàmetre del centre" + zoomLinesBlack: "Obscurir" + circle: "Cercle" +drafts: "Esborrany " +_drafts: + select: "Seleccionar esborrany" + cannotCreateDraftAnymore: "S'ha sobrepassat el nombre màxim d'esborranys que es poden crear." + cannotCreateDraft: "Amb aquest contingut no es poden crear esborranys." + delete: "Esborrar esborranys" + deleteAreYouSure: "Vols esborrar els esborranys?" + noDrafts: "No hi ha esborranys" + replyTo: "Respondre a {user}" + quoteOf: "Citar les notes de {user}" + postTo: "Destinat a {channel}" + saveToDraft: "Desar com a esborrany" + restoreFromDraft: "Restaurar des dels esborranys" + restore: "Restaurar esborrany" + listDrafts: "Llistat d'esborranys" + schedule: "Programació esborranys" + listScheduledNotes: "Llista de notes programades" + cancelSchedule: "Cancel·lar la programació" +qr: "Codi QR" +_qr: + showTabTitle: "Veure" + readTabTitle: "Escanejar " + shareTitle: "{name} {acct}" + shareText: "Segueix-me al Fediverse" + chooseCamera: "Seleccionar càmera " + cannotToggleFlash: "No es pot activar el flaix" + turnOnFlash: "Activar el flaix" + turnOffFlash: "Apagar el flaix" + startQr: "Reiniciar el lector de codis QR" + stopQr: "Parar el lector de codis QR" + noQrCodeFound: "No s'ha trobat cap codi QR" + scanFile: "Escanejar la imatge des del dispositiu" + raw: "Text" + mfm: "MFM" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 87f9445409..22962a4243 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1,7 +1,7 @@ --- _lang_: "Čeština" headlineMisskey: "Síť propojená poznámkami" -introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový servis.\n\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. 📡\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. 👍\nPojďte objevovat nový svět! 🚀" +introMisskey: "Vítejte! Misskey je otevřená a decentralizovaná microblogovací služba.\n\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. 📡\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. 👍\nPojďte objevovat nový svět! 🚀" poweredByMisskeyDescription: "{name} je jeden ze serverů využívající open source platformu Misskey (nazývaná \"Misskey instance\")." monthAndDay: "{day}. {month}." search: "Vyhledávání" @@ -19,7 +19,7 @@ gotIt: "Rozumím!" cancel: "Zrušit" noThankYou: "Ne děkuji" enterUsername: "Zadej uživatelské jméno" -renotedBy: "{user} přeposla/a" +renotedBy: "{user} přeposlal*a" noNotes: "Žádné poznámky" noNotifications: "Žádná oznámení" instance: "Instance" @@ -65,6 +65,7 @@ copyFileId: "Kopírovat ID souboru" copyFolderId: "Kopírovat ID složky" copyProfileUrl: "Kopírovat URL profilu" searchUser: "Vyhledat uživatele" +searchThisUsersNotes: "Prohledat poznámky uživatele" reply: "Odpovědět" loadMore: "Zobrazit více" showMore: "Zobrazit více" @@ -228,7 +229,6 @@ noUsers: "Žádní uživatelé" editProfile: "Upravit můj profil" noteDeleteConfirm: "Jste si jistí že chcete smazat tuhle poznámku?" pinLimitExceeded: "Nemůžete připnout další poznámky." -intro: "Instalace Misskey byla dokončena! Prosím vytvořte admina." done: "Hotovo" processing: "Zpracovávám" preview: "Náhled" @@ -726,7 +726,6 @@ thisIsExperimentalFeature: "Tohle je experimentální funkce. Její funkce se m developer: "Vývojář" makeExplorable: "Udělat účet viditelný v \"Objevit\"" makeExplorableDescription: "Pokud tohle vypnete, tak se účet přestane zobrazovat v sekci \"Objevit\"." -showGapBetweenNotesInTimeline: "Zobrazit mezeru mezi příspěvkama na časové ose" duplicate: "Duplikovat" left: "Vlevo" center: "Uprostřed" @@ -1108,6 +1107,18 @@ lastNDays: "Posledních {n} dnů" surrender: "Zrušit" postForm: "Formulář pro odeslání" information: "Informace" +inMinutes: "Minut" +inDays: "Dnů" +widgets: "Widgety" +presets: "Předvolba" +_imageEditing: + _vars: + filename: "Název souboru" +_imageFrameEditor: + header: "Nadpis" + font: "Písmo" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "Pozvat" noHistory: "Žádná historie" @@ -1647,7 +1658,6 @@ _theme: buttonBg: "Pozadí tlačítka" buttonHoverBg: "Pozadí tlačítka (Hover)" inputBorder: "Ohraničení vstupního pole" - driveFolderBg: "Pozadí složky disku" badge: "Odznak" messageBg: "Pozadí chatu" fgHighlighted: "Zvýrazněný text" @@ -1779,6 +1789,14 @@ _widgets: _userList: chooseList: "Vybrat seznam" clicker: "Clicker" +_widgetOptions: + height: "Výška" + _button: + colored: "Barevné" + _clock: + size: "Velikost" + _birthdayFollowings: + period: "Trvání" _cw: hide: "Skrýt" show: "Zobrazit více" @@ -1821,6 +1839,9 @@ _postForm: replyPlaceholder: "Odpovědět na tuto poznámku..." quotePlaceholder: "Citovat tuto poznámku..." channelPlaceholder: "Zveřejnit příspěvek do kanálu..." + _howToUse: + visibility_title: "Viditelnost" + menu_title: "Menu" _placeholders: a: "Co máte v plánu?" b: "Co se děje kolem vás?" @@ -2004,7 +2025,7 @@ _deck: list: "Seznamy" channel: "Kanály" mentions: "Zmínění" - direct: "Přímý" + direct: "Přímé poznámky" roleTimeline: "Časová osa role" _dialog: charactersExceeded: "Překročili jste maximální počet znaků! V současné době je na hodnotě {current} z {max}." @@ -2045,3 +2066,22 @@ _search: searchScopeAll: "Vše" searchScopeLocal: "Místní" searchScopeUser: "Upřesnit uživatele" +_watermarkEditor: + opacity: "Průhlednost" + scale: "Velikost" + text: "Text" + position: "Pozice" + type: "Typ" + image: "Obrázky" + advanced: "Pokročilé" +_imageEffector: + _fxProps: + scale: "Velikost" + size: "Velikost" + offset: "Pozice" + color: "Barva" + opacity: "Průhlednost" + lightness: "Zesvětlit" +_qr: + showTabTitle: "Zobrazit" + raw: "Text" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 237603299c..beebba8292 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -83,6 +83,8 @@ files: "Dateien" download: "Herunterladen" driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Einige Inhalte, die diese Datei verwenden, werden auch verschwinden." unfollowConfirm: "Möchtest du {name} wirklich nicht mehr folgen?" +cancelFollowRequestConfirm: "Möchten Sie die Voll-Anfrage an {name} zurückziehen?" +rejectFollowRequestConfirm: "Möchtest du die Follow-Anfrage von {name} ablehnen?" exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt." importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen." lists: "Listen" @@ -220,6 +222,7 @@ silenceThisInstance: "Instanz stummschalten" mediaSilenceThisInstance: "Medien dieses Servers stummschalten" operations: "Aktionen" software: "Software" +softwareName: "Software Name" version: "Version" metadata: "Metadaten" withNFiles: "{n} Datei(en)" @@ -250,9 +253,9 @@ noUsers: "Keine Benutzer gefunden" editProfile: "Profil bearbeiten" noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?" pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften." -intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten." done: "Fertig" processing: "In Bearbeitung …" +preprocessing: "In Vorbereitung" preview: "Vorschau" default: "Standard" defaultValueIs: "Standardwert: {value}" @@ -298,8 +301,10 @@ uploadFromUrl: "Von einer URL hochladen" uploadFromUrlDescription: "URL der hochzuladenden Datei" uploadFromUrlRequested: "Upload angefordert" uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschlossen ist." +uploadNFiles: "Lade {n} Dateien hoch" explore: "Erkunden" messageRead: "Gelesen" +readAllChatMessages: "Alle Nachrichten als gelesen markieren" noMoreHistory: "Kein weiterer Verlauf vorhanden" startChat: "Chat starten" nUsersRead: "Von {n} Benutzern gelesen" @@ -326,11 +331,13 @@ dark: "Dunkel" lightThemes: "Helle Farbschemata" darkThemes: "Dunkle Farbschemata" syncDeviceDarkMode: "Einstellung deines Geräts übernehmen" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" ist eingeschaltet. Möchtest du die Synchronisation ausschalten und den Modus manuell wechseln?" drive: "Drive" fileName: "Dateiname" selectFile: "Datei auswählen" selectFiles: "Dateien auswählen" selectFolder: "Ordner auswählen" +unselectFolder: "Ordnerauswahl aufheben" selectFolders: "Ordner auswählen" fileNotSelected: "Keine Datei ausgewählt" renameFile: "Datei umbenennen" @@ -343,6 +350,7 @@ addFile: "Datei hinzufügen" showFile: "Datei anzeigen" emptyDrive: "Deine Drive ist leer" emptyFolder: "Dieser Ordner ist leer" +dropHereToUpload: "Dateien hier ablegen, um sie hochzuladen." unableToDelete: "Nicht löschbar" inputNewFileName: "Gib einen neuen Dateinamen ein" inputNewDescription: "Gib eine neue Beschreibung ein" @@ -575,8 +583,10 @@ showFixedPostForm: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik an showFixedPostFormInChannel: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen (Kanäle)" withRepliesByDefaultForNewlyFollowed: "Standardmäßig Antworten von neu gefolgten Benutzern in der Chronik anzeigen" newNoteRecived: "Es gibt neue Notizen" +newNote: "Neue Notiz" sounds: "Töne" sound: "Töne" +notificationSoundSettings: "Benachrichtigungston festlegen" listen: "Anhören" none: "Nichts" showInPage: "In einer Seite anzeigen" @@ -768,6 +778,7 @@ lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt alwaysMarkSensitive: "Medien standardmäßig als sensibel markieren" loadRawImages: "Anstatt Vorschaubilder immer Originalbilder anzeigen" disableShowingAnimatedImages: "Animierte Bilder nicht abspielen" +disableShowingAnimatedImages_caption: "Unabhängig von dieser Einstellung kann es vorkommen, dass animierte Bilder nicht abgespielt werden, wenn z. B. die Barrierefreiheits- oder Energiespareinstellungen des Browsers oder des Betriebssystems eingreifen." highlightSensitiveMedia: "Sensitive Medien markieren" verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen." notSet: "Nicht konfiguriert" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "Dies ist eine experimentelle Funktion. Änderungen a developer: "Entwickler" makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen" makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar." -showGapBetweenNotesInTimeline: "Abstände zwischen Notizen auf der Chronik anzeigen" duplicate: "Duplizieren" left: "Links" center: "Mittig" @@ -792,6 +802,7 @@ wide: "Breit" narrow: "Schmal" reloadToApplySetting: "Diese Einstellung tritt nach einer Aktualisierung der Seite in Kraft. Jetzt aktualisieren?" needReloadToApply: "Diese Einstellung tritt nach einer Aktualisierung der Seite in Kraft." +needToRestartServerToApply: "Diese Einstellung tritt nach einem Neustart des Servers in Kraft." showTitlebar: "Titelleiste anzeigen" clearCache: "Cache leeren" onlineUsersCount: "{n} Benutzer sind online" @@ -979,6 +990,7 @@ document: "Dokumentation" numberOfPageCache: "Seitencachegröße" numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, aber erhöht Last und Arbeitsspeicherauslastung auf dem Nutzergerät." logoutConfirm: "Wirklich abmelden?" +logoutWillClearClientData: "Beim Abmelden werden die Konfigurationsdaten des Clients aus dem Browser gelöscht. Um sicherzustellen, dass die Konfigurationsdaten beim erneuten Einloggen wiederhergestellt werden können, aktivieren Sie bitte die automatische Sicherung der Konfiguration." lastActiveDate: "Zuletzt verwendet am" statusbar: "Statusleiste" pleaseSelect: "Wähle eine Option" @@ -997,6 +1009,7 @@ failedToUpload: "Hochladen fehlgeschlagen" cannotUploadBecauseInappropriate: "Diese Datei kann nicht hochgeladen werden, da Anteile der Datei als möglicherweise unangebracht festgestellt wurden." cannotUploadBecauseNoFreeSpace: "Die Datei konnte nicht hochgeladen werden, da dein Drive-Speicherplatz aufgebraucht ist." cannotUploadBecauseExceedsFileSizeLimit: "Diese Datei kann wegen Überschreitung der Maximalgröße nicht hochgeladen werden." +cannotUploadBecauseUnallowedFileType: "Hochladen nicht möglich wegen unzulässigem Dateityp." beta: "Beta" enableAutoSensitive: "Automarkierung sensibler Medien" enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Machine Learning automatisch Markierungen für sensible Medien. Auch wenn du diese Option deaktiviert hast, ist sie möglicherweise auf Instanzebene aktiviert." @@ -1012,6 +1025,9 @@ pushNotificationAlreadySubscribed: "Push-Benachrichtigungen sind bereits aktivie pushNotificationNotSupported: "Entweder dein Browser oder deine Instanz unterstützt Push-Benachrichtigungen nicht" sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald sie gelesen wurden" sendPushNotificationReadMessageCaption: "Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen." +pleaseAllowPushNotification: "Bitte erlauben Sie Benachrichtigungen in Ihrem Browser." +browserPushNotificationDisabled: "Das Abrufen der Berechtigung zum Senden von Benachrichtigungen ist fehlgeschlagen." +browserPushNotificationDisabledDescription: "Sie haben keine Berechtigung, Benachrichtigungen von {serverName} zu senden. Bitte erlauben Sie Benachrichtigungen in den Browser-Einstellungen und versuchen Sie es erneut." windowMaximize: "Maximieren" windowMinimize: "Minimieren" windowRestore: "Wiederherstellen" @@ -1048,6 +1064,7 @@ permissionDeniedError: "Aktion verweigert" permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen." preset: "Vorlage" selectFromPresets: "Aus Vorlagen wählen" +custom: "Benutzerdefiniert" achievements: "Errungenschaften" gotInvalidResponseError: "Ungültige Antwort des Servers" gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal." @@ -1086,6 +1103,7 @@ prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-V hiddenTags: "Ausgeblendete Hashtags" hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden." notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar." +usersSearchNotAvailable: "Die Benutzersuche ist nicht verfügbar." license: "Lizenz" unfavoriteConfirm: "Wirklich aus Favoriten entfernen?" myClips: "Meine Clips" @@ -1160,6 +1178,7 @@ installed: "Installiert" branding: "Branding" enableServerMachineStats: "Hardwareinformationen des Servers veröffentlichen" enableIdenticonGeneration: "Generierung von Benutzer-Identicons aktivieren" +showRoleBadgesOfRemoteUsers: "Rollensymbole anzeigen, die Remote-Benutzern zugewiesen wurden." turnOffToImprovePerformance: "Deaktivierung kann zu höherer Leistung führen." createInviteCode: "Einladung erstellen" createWithOptions: "Einladung mit Optionen erstellen" @@ -1236,9 +1255,8 @@ showAvatarDecorations: "Profilbilddekoration anzeigen" releaseToRefresh: "Zum Aktualisieren loslassen" refreshing: "Wird aktualisiert..." pullDownToRefresh: "Zum Aktualisieren ziehen" -disableStreamingTimeline: "Echtzeitaktualisierung der Chronik deaktivieren" useGroupedNotifications: "Benachrichtigungen gruppieren" -signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. Der Link könnte abgelaufen sein." +emailVerificationFailedError: "Es gab ein Problem bei der Überprüfung Ihrer E-Mail-Adresse. Der Link ist möglicherweise abgelaufen." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" @@ -1308,6 +1326,8 @@ availableRoles: "Verfügbare Rollen" acknowledgeNotesAndEnable: "Schalten Sie dies erst ein, wenn Sie die Vorsichtsmaßnahmen verstanden haben." federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren." federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren." +draft: "Entwurf" +draftsAndScheduledNotes: "Entwürfe und geplante Beiträge" confirmOnReact: "Reagieren bestätigen" reactAreYouSure: "Willst du eine \"{emoji}\"-Reaktion hinzufügen?" markAsSensitiveConfirm: "Möchtest du dieses Medium als sensibel kennzeichnen?" @@ -1325,6 +1345,7 @@ restore: "Wiederherstellen" syncBetweenDevices: "Zwischen Geräten synchronisieren" preferenceSyncConflictTitle: "Der konfigurierte Wert ist auf dem Server bereits vorhanden." preferenceSyncConflictText: "Die Einstellungen mit aktivierter Synchronisierung werden ihre Werte auf dem Server speichern. Es gibt jedoch bereits Werte auf dem Server. Welche Einstellungswerte sollen überschrieben werden?" +preferenceSyncConflictChoiceMerge: "Zusammenführen" preferenceSyncConflictChoiceServer: "Konfigurierte Werte auf dem Server" preferenceSyncConflictChoiceDevice: "Konfigurierte Werte auf dem Gerät" preferenceSyncConflictChoiceCancel: "Einrichten der Synchronisierung abbrechen" @@ -1334,6 +1355,8 @@ postForm: "Notizfenster" textCount: "Zeichenanzahl" information: "Über" chat: "Chat" +directMessage: "Mit dem Benutzer chatten" +directMessage_short: "Nachrichten" migrateOldSettings: "Alte Client-Einstellungen migrieren" migrateOldSettings_description: "Dies sollte normalerweise automatisch geschehen, aber wenn die Migration aus irgendeinem Grund nicht erfolgreich war, kannst du den Migrationsprozess selbst manuell auslösen. Die aktuellen Konfigurationsinformationen werden dabei überschrieben." compress: "Komprimieren" @@ -1341,10 +1364,74 @@ right: "Rechts" bottom: "Unten" top: "Oben" embed: "Einbetten" -settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie einen Moment... (Sie können die Einstellungen später auch manuell migrieren, indem Sie zu Einstellungen → Sonstiges → Alte Einstellungen migrieren gehen)" +settingsMigrating: "Deine Einstellungen werden gerade migriert. Bitte warte einen Moment... (Du kannst die Einstellungen später auch manuell migrieren, indem du zu Einstellungen → Anderes → Alte Einstellungen migrieren gehst)" readonly: "Nur Lesezugriff" goToDeck: "Zurück zum Deck" +federationJobs: "Föderation Jobs" +driveAboutTip: "In Drive sehen Sie eine Liste der Dateien, die Sie in der Vergangenheit hochgeladen haben.
\nSie können diese Dateien wiederverwenden um sie zu beispiel an Notizen anzuhängen, oder sie können Dateien vorab hochzuladen, um sie später zu versenden!
\nWenn Sie eine Datei löschen, verschwindet sie auch von allen Stellen, an denen Sie sie verwendet haben (Notizen, Seiten, Avatare, Banner usw.).
\nSie können auch Ordner erstellen, um sie zu organisieren." +scrollToClose: "Zum Schließen scrollen" +advice: "Tipps" +realtimeMode: "Echtzeit-Modus" +turnItOn: "Einschalten" +turnItOff: "Ausschalten" +emojiMute: "Emoji stummschalten" +emojiUnmute: "Emoji-Stummschaltung aufheben" +muteX: "{x} stummschalten" +unmuteX: "Stummschaltung von {x} aufheben" +abort: "Abbrechen" +tip: "Tipps und Tricks" +redisplayAllTips: "Alle „Tipps und Tricks“ wieder anzeigen" +hideAllTips: "Alle „Tipps und Tricks“ ausblenden" +defaultImageCompressionLevel: "Standard-Bildkomprimierungsstufe" +defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße.
Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität." +defaultCompressionLevel: "Standard-Kompressionsgrad" +defaultCompressionLevel_description: "Bei einem niedrigeren Wert bleibt die Qualität erhalten, aber die Dateigröße nimmt zu.
Bei einem höheren Wert lässt sich die Dateigröße verringern, aber die Qualität nimmt ab." +inMinutes: "Minute(n)" +inDays: "Tag(en)" +safeModeEnabled: "Der abgesicherte Modus ist aktiviert." +pluginsAreDisabledBecauseSafeMode: "Da der abgesicherte Modus aktiviert ist, sind alle Plugins deaktiviert." +customCssIsDisabledBecauseSafeMode: "Da der abgesicherte Modus aktiviert ist, wird benutzerdefiniertes CSS nicht angewendet." +themeIsDefaultBecauseSafeMode: "Solange der abgesicherte Modus aktiviert ist, wird das Standard-Theme verwendet. Wenn Sie den abgesicherten Modus deaktivieren, wird es wieder zurückgesetzt." +thankYouForTestingBeta: "Vielen Dank für Ihre Unterstützung beim Testen der Beta-Version!" +createUserSpecifiedNote: "Benutzerdefinierte Notiz erstellen" +schedulePost: "Beitrag planen" +scheduleToPostOnX: "Der Beitrag wird für {x} geplant.x" +scheduledToPostOnX: "Der Beitrag ist für {x} geplant." +schedule: "Planen" +scheduled: "Geplant" +widgets: "Widgets" +deviceInfo: "Geräteinformation" +deviceInfoDescription: "Bei technischen Anfragen kann es hilfreich sein, die folgenden Informationen anzugeben, da dies zur Lösung des Problems beitragen kann." +youAreAdmin: "Sie sind ein Administrator" +frame: "Rahmen" +presets: "Vorlage" +zeroPadding: "Nullauffüllung" +_imageEditing: + _vars: + caption: "Dateibeschriftung" + filename: "Dateiname" + filename_without_ext: "Dateiname ohne Erweiterung" + year: "Jahr der Aufnahme" + month: "Monat der Aufnahme" + day: "Tag der Aufnahme" + hour: "Stunde der Aufnahmezeit" + minute: "Minute der Aufnahmezeit" + second: "Sekunde der Aufnahmezeit" + camera_model: "Kameraname" + camera_lens_model: "Objektivname" + camera_mm: "Brennweite" + camera_mm_35: "Brennweite (35-mm-Äquivalent)" +_imageFrameEditor: + header: "Kopfzeile" + font: "Schriftart" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "Nicht gespeicherte Änderungen verwerfen?" +_order: + newest: "Neueste zuerst" + oldest: "Älteste zuerst" _chat: + messages: "Nachrichten" noMessagesYet: "Noch keine Nachrichten" newMessage: "Neue Nachricht" individualChat: "Privater Chat" @@ -1377,6 +1464,8 @@ _chat: chatNotAvailableInOtherAccount: "Die Chatfunktion wurde vom anderen Benutzer deaktiviert." cannotChatWithTheUser: "Starten eines Chats mit diesem Benutzer nicht möglich" cannotChatWithTheUser_description: "Der Chat ist entweder nicht verfügbar oder die andere Seite hat den Chat nicht aktiviert." + youAreNotAMemberOfThisRoomButInvited: "Du bist kein Teilnehmer in diesem Raum, aber du hast eine Einladung erhalten. Bitte nimm die Einladung an, um beizutreten." + doYouAcceptInvitation: "Nimmst du die Einladung an?" chatWithThisUser: "Mit dem Benutzer chatten" thisUserAllowsChatOnlyFromFollowers: "Dieser Benutzer nimmt nur Chats von Followern an." thisUserAllowsChatOnlyFromFollowing: "Dieser Benutzer nimmt nur Chats von Benutzern an, denen er folgt." @@ -1416,10 +1505,20 @@ _settings: makeEveryTextElementsSelectable: "Alle Textelemente auswählbar machen" makeEveryTextElementsSelectable_description: "Die Aktivierung kann in manchen Situationen die Benutzerfreundlichkeit beeinträchtigen." useStickyIcons: "Icons beim Scrollen folgen lassen" + enableHighQualityImagePlaceholders: "Zeige Platzhalter für Bilder in hoher Qualität an" + uiAnimations: "Animationen der Benutzeroberfläche" showNavbarSubButtons: "Unterschaltflächen in der Navigationsleiste anzeigen" ifOn: "Wenn eingeschaltet" ifOff: "Wenn ausgeschaltet" enableSyncThemesBetweenDevices: "Synchronisierung von installierten Themen auf verschiedenen Endgeräten" + enablePullToRefresh: "Ziehen zum Aktualisieren" + enablePullToRefresh_description: "Bei Benutzung einer Maus, mit gedrücktem Mausrad ziehen" + realtimeMode_description: "Stellt eine Verbindung mit dem Server her und aktualisiert die Inhalte in Echtzeit. Kann zu mehr Datenverkehr einem höheren Akkuverbrauch führen." + contentsUpdateFrequency: "Häufigkeit des Abrufs von Inhalten" + contentsUpdateFrequency_description: "Je höher der Wert, desto häufiger werden die Inhalte aktualisiert, aber die Leistung sinkt und der Datenverkehr und der Akkuverbrauch steigen." + contentsUpdateFrequency_description2: "Wenn der Echtzeitmodus aktiviert ist, werden die Inhalte unabhängig von dieser Einstellung in Echtzeit aktualisiert." + showUrlPreview: "URL-Vorschau anzeigen" + showAvailableReactionsFirstInNote: "Zeige die verfügbaren Reaktionen im oberen Bereich an." _chat: showSenderName: "Name des Absenders anzeigen" sendOnEnter: "Eingabetaste sendet Nachricht" @@ -1427,6 +1526,7 @@ _preferencesProfile: profileName: "Profilname" profileNameDescription: "Lege einen Namen fest, der dieses Gerät identifiziert." profileNameDescription2: "Beispiel: \"Haupt-PC\", \"Smartphone\"" + manageProfiles: "Profile verwalten" _preferencesBackup: autoBackup: "Automatische Sicherung" restoreFromBackup: "Wiederherstellen aus der Sicherung" @@ -1465,6 +1565,7 @@ _delivery: manuallySuspended: "Manuell gesperrt" goneSuspended: "Gesperrt wegen Löschung des Servers" autoSuspendedForNotResponding: "Gesperrt, weil der Server nicht antwortet" + softwareSuspended: "Ausgesetzt, weil die Software nicht mehr beliefert wird" _bubbleGame: howToPlay: "Wie man spielt" hold: "Halten" @@ -1596,6 +1697,23 @@ _serverSettings: openRegistration: "Registrierung von Konten aktivieren" openRegistrationWarning: "Das Aktivieren von Registrierungen ist riskant. Es wird empfohlen, sie nur dann zu aktivieren, wenn der Server ständig überwacht wird und im Falle eines Problems sofort reagiert werden kann." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern." + deliverSuspendedSoftware: "Software, die nicht mehr beliefert wird" + deliverSuspendedSoftwareDescription: "Sie können eine Auswahl von Namen und Versionen verschiedener Serversoftware angeben, um die Zustellung zu stoppen, z. B. aufgrund von Sicherheitslücken. Diese Versionsinformationen werden vom Server bereitgestellt und ihre Zuverlässigkeit ist nicht garantiert. Es wird jedoch empfohlen, eine Vorabversion anzugeben, wie z. B. >= 2024.3.1-0, da die Angabe >= 2024.3.1 keine benutzerdefinierten Versionen wie 2024.3.1-custom.0 einschließt." + singleUserMode: "Einzelbenutzermodus" + singleUserMode_description: "Wenn du der einzige Benutzer dieses Servers bist, optimiert die Aktivierung dieses Modus die Leistung des Servers." + signToActivityPubGet: "ActivityPub-GET-Anfragen signieren" + signToActivityPubGet_description: "Normalerweise sollte diese Option aktiviert sein. Die Deaktivierung kann Probleme im Zusammenhang mit der Föderation beheben, aber andererseits könnte sie die Föderation mit einigen anderen Servern deaktivieren." + proxyRemoteFiles: "Proxy für Dateien fremder Instanzen" + proxyRemoteFiles_description: "Wenn diese Einstellung aktiviert ist, werden fremde Dateien über einen Proxyserver übertragen und bereitgestellt. Dies hilft bei der Erstellung von Vorschaubildern und schützt die Privatsphäre der Benutzer." + allowExternalApRedirect: "Weiterleitungen für Anfragen über ActivityPub zulassen" + allowExternalApRedirect_description: "Wenn diese Option aktiviert ist, können andere Server Inhalte von Drittanbietern über diesen Server abfragen, was jedoch zu Content-Spoofing führen kann." + userGeneratedContentsVisibilityForVisitor: "Sichtbarkeit von nutzergenerierten Inhalten für Gäste" + userGeneratedContentsVisibilityForVisitor_description: "Dies ist nützlich, um zu verhindern, dass unangemessene Inhalte, die nicht gut moderiert sind, ungewollt über deinen eigenen Server im Internet veröffentlicht werden." + userGeneratedContentsVisibilityForVisitor_description2: "Die uneingeschränkte Veröffentlichung aller Inhalte des Servers im Internet, einschließlich der vom Server empfangenen Fremdinhalte, birgt Risiken. Dies ist besonders wichtig für Betrachter, die sich des dezentralen Charakters der Inhalte nicht bewusst sind, da sie selbst fremde Inhalte fälschlicherweise als auf dem Server erstellte Inhalte wahrnehmen könnten." + _userGeneratedContentsVisibilityForVisitor: + all: "Alles ist öffentlich" + localOnly: "Nur lokale Inhalte werden veröffentlicht, fremde Inhalte bleiben privat" + none: "Alles ist privat" _accountMigration: moveFrom: "Von einem anderen Konto zu diesem migrieren" moveFromSub: "Alias für ein anderes Konto erstellen" @@ -1913,6 +2031,7 @@ _role: canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten" canManageAvatarDecorations: "Profilbilddekorationen verwalten" driveCapacity: "Drive-Kapazität" + maxFileSize: "Maximale Dateigröße, die hochgeladen werden kann" alwaysMarkNsfw: "Dateien immer als NSFW markieren" canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten" pinMax: "Maximale Anzahl an angehefteten Notizen" @@ -1935,6 +2054,11 @@ _role: canImportMuting: "Importieren von Stummgeschalteten zulassen" canImportUserLists: "Importieren von Listen erlauben" chatAvailability: "Chatten erlauben" + uploadableFileTypes: "Hochladbare Dateitypen" + uploadableFileTypes_caption: "Gibt die zulässigen MIME-/Dateitypen an. Mehrere MIME-Typen können durch einen Zeilenumbruch getrennt angegeben werden, und Platzhalter können mit einem Sternchen (*) angegeben werden. (z. B. image/*)" + uploadableFileTypes_caption2: "Bei manchen Dateien ist es nicht möglich, den Typ zu bestimmen. Um solche Dateien zuzulassen, füge {x} der Spezifikation hinzu." + noteDraftLimit: "Anzahl der möglichen Entwürfe für serverseitige Notizen" + watermarkAvailable: "Kann die Wasserzeichenfunktion verwenden" _condition: roleAssignedTo: "Manuellen Rollen zugewiesen" isLocal: "Lokaler Benutzer" @@ -2094,6 +2218,7 @@ _theme: install: "Farbschemata installieren" manage: "Farbschemaverwaltung" code: "Farbschemencode" + copyThemeCode: "Farbschemencode kopieren" description: "Beschreibung" installed: "{name} wurde installiert" installedThemes: "Installierte Farbschemata" @@ -2152,7 +2277,6 @@ _theme: buttonBg: "Hintergrund von Schaltflächen" buttonHoverBg: "Hintergrund von Schaltflächen (Mouseover)" inputBorder: "Rahmen von Eingabefeldern" - driveFolderBg: "Hintergrund von Drive-Ordnern" badge: "Wappen" messageBg: "Hintergrund von Chats" fgHighlighted: "Hervorgehobener Text" @@ -2368,7 +2492,16 @@ _widgets: chooseList: "Liste auswählen" clicker: "Klickzähler" birthdayFollowings: "Nutzer, die heute Geburtstag haben" - chat: "Chat" + chat: "Mit dem Benutzer chatten" +_widgetOptions: + showHeader: "Kopfzeile anzeigen" + height: "Höhe" + _button: + colored: "Farbig" + _clock: + size: "Größe" + _birthdayFollowings: + period: "Dauer" _cw: hide: "Inhalt verbergen" show: "Inhalt anzeigen" @@ -2408,9 +2541,14 @@ _visibility: disableFederation: "Deföderieren" disableFederationDescription: "Nicht an andere Instanzen übertragen" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Es gibt Dateien, die nicht hochgeladen wurden. Möchtest du diese verwerfen und das Formular schließen?" + uploaderTip: "Die Datei wurde noch nicht hochgeladen. Über das Dateimenü kannst du sie umbenennen, das Bild zuschneiden, ein Wasserzeichen hinzufügen, komprimieren usw. Die Datei wird automatisch hochgeladen, wenn du eine Notiz veröffentlichst." replyPlaceholder: "Dieser Notiz antworten …" quotePlaceholder: "Diese Notiz zitieren …" channelPlaceholder: "In einen Kanal senden" + _howToUse: + visibility_title: "Sichtbarkeit" + menu_title: "Menü" _placeholders: a: "Was machst du momentan?" b: "Was ist um dich herum los?" @@ -2635,7 +2773,7 @@ _deck: mentions: "Erwähnungen" direct: "Direktnachrichten" roleTimeline: "Rollenchronik" - chat: "Chat" + chat: "Mit dem Benutzer chatten" _dialog: charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}" charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}" @@ -2788,9 +2926,12 @@ _dataSaver: _avatar: title: "Animierte Profilbilder deaktivieren" description: "Die Animation von Profilbildern wird angehalten. Da animierte Bilder eine größere Dateigröße haben können als normale Bilder, kann dies den Datenverkehr weiter reduzieren." - _urlPreview: + _urlPreviewThumbnail: title: "URL-Vorschaubilder ausblenden" description: "URL-Vorschaubilder werden nicht mehr geladen." + _disableUrlPreview: + title: "URL-Vorschau deaktivieren" + description: "Deaktiviert die URL-Vorschaufunktion. Anders als bei reinen Vorschaubildern wird dadurch das Laden der verlinkten Informationen selbst reduziert." _code: title: "Code-Hervorhebungen ausblenden" description: "Wenn Code-Hervorhebungen in MFM usw. verwendet werden, werden sie erst geladen, wenn sie angetippt werden. Die Syntaxhervorhebung erfordert das Herunterladen der Definitionsdateien für jede Programmiersprache. Es ist daher zu erwarten, dass die Deaktivierung des automatischen Ladens dieser Dateien die Menge des Datenverkehrs reduziert." @@ -2848,6 +2989,8 @@ _offlineScreen: _urlPreviewSetting: title: "Einstellungen der URL-Vorschau" enable: "URL-Vorschau aktivieren" + allowRedirect: "Umleitung von URL-Vorschauen erlauben" + allowRedirectDescription: "Wenn für eine URL eine Umleitung festgelegt ist, kann diese Funktion aktiviert werden, um der Umleitung zu folgen und eine Vorschau des umgeleiteten Inhalts anzuzeigen. Die Deaktivierung spart Serverressourcen, aber der Inhalt des Weiterleitungsziels wird nicht angezeigt." timeout: "Zeitüberschreitung beim Abrufen der Vorschau (ms)" timeoutDescription: "Übersteigt die für die Vorschau benötigte Zeit diesen Wert, wird keine Vorschau generiert." maximumContentLength: "Maximale Content-Length (Bytes)" @@ -2921,10 +3064,6 @@ _customEmojisManager: uploadSettingDescription: "Hier kannst du das Verhalten beim Hochladen von Emojis konfigurieren." directoryToCategoryLabel: "Gib den Namen des Verzeichnisses in das Feld „Kategorie“ ein" directoryToCategoryCaption: "Wenn du ein Verzeichnis ziehst und ablegst, gib den Verzeichnisnamen in das Feld „Kategorie“ ein." - emojiInputAreaCaption: "Wählen Sie die Emojis aus, die Sie mit einer der folgenden Methoden speichern möchten." - emojiInputAreaList1: "Ziehe Bilddateien oder Verzeichnisse per Drag-and-drop in diesen Rahmen" - emojiInputAreaList2: "Klicke auf diesen Link, um von deinem PC aus zu wählen" - emojiInputAreaList3: "Klicke auf diesen Link, um vom Drive aus zu wählen" confirmRegisterEmojisDescription: "Füge die in der Liste aufgeführten Emojis als neue benutzerdefinierte Emojis hinzu. Bist du sicher? (Um eine Überlastung zu vermeiden, können nur {count} Emoji(s) in einem Vorgang hinzugefügt werden)" confirmClearEmojisDescription: "Verwerfe die Bearbeitungen und lösche die Emojis aus der Liste. Bist du sicher, dass du fortfahren möchtest?" confirmUploadEmojisDescription: "Lade die {count} abgelegte(n) Datei(en) in das Drive hoch. Bist du sicher, dass du fortfahren möchtest?" @@ -3000,3 +3139,132 @@ _search: pleaseEnterServerHost: "Gib den Server-Host ein" pleaseSelectUser: "Benutzer auswählen" serverHostPlaceholder: "Beispiel: misskey.example.com" +_serverSetupWizard: + installCompleted: "Die Installation von Misskey ist abgeschlossen!" + firstCreateAccount: "Erstelle zunächst ein Administratorkonto." + accountCreated: "Ein Administratorkonto wurde angelegt!" + serverSetting: "Servereinstellungen" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Mit diesem Assistenten lässt sich die optimale Serverkonfiguration leicht einrichten." + settingsYouMakeHereCanBeChangedLater: "Die Einstellungen hier können später geändert werden." + howWillYouUseMisskey: "Wie wirst du Misskey verwenden?" + _use: + single: "Ein-Personen-Server" + single_description: "Verwende den Server alleine als deinen eigenen." + single_youCanCreateMultipleAccounts: "Bei Bedarf können mehrere Konten eingerichtet werden, auch wenn es sich um einen Ein-Personen-Server handelt." + group: "Gruppenserver" + group_description: "Lade andere vertrauenswürdige Benutzer ein und verwende es mit mehreren Personen." + open: "Offener Server" + open_description: "Registrierung für alle öffnen." + openServerAdvice: "Die Aufnahme einer unbestimmten Anzahl von Nutzern birgt Risiken. Es wird empfohlen, mit einem zuverlässigen Moderationssystem zu arbeiten, um eventuell auftretende Probleme behandeln zu können." + openServerAntiSpamAdvice: "Große Sorgfalt muss auch auf die Sicherheit gelegt werden, z. B. durch die Aktivierung von Anti-Bot-Funktionen wie reCAPTCHA, um sicherzustellen, dass der Server nicht zum Verbreiten von Spam genutzt wird." + howManyUsersDoYouExpect: "Mit wie vielen Benutzern rechnest du?" + _scale: + small: "Weniger als 100 (kleiner Maßstab)" + medium: "Mehr als 100 und weniger als 1000 Benutzer (mittelgroß)" + large: "Mehr als 1000 (großer Maßstab)" + largeScaleServerAdvice: "Für große Server sind unter Umständen fortgeschrittene Kenntnisse erforderlich, z. B. Lastverteilung und Datenbankreplikation." + doYouConnectToFediverse: "Mit dem Fediverse verbinden?" + doYouConnectToFediverse_description1: "Bei Anschluss an ein Netz von verteilten Servern (Fediverse) können Inhalte mit anderen Servern ausgetauscht werden." + doYouConnectToFediverse_description2: "Die Verbindung mit dem Fediverse wird auch als „Föderation“ bezeichnet." + youCanConfigureMoreFederationSettingsLater: "Erweiterte Einstellungen, wie z. B. die Angabe von föderierbaren Servern, können später vorgenommen werden." + adminInfo: "Administrator-Informationen" + adminInfo_description: "Legt die Administrator-Informationen fest, die für den Empfang von Anfragen verwendet werden." + adminInfo_mustBeFilled: "Dies ist auf einem offenen Server oder bei aktivierter Föderation erforderlich." + followingSettingsAreRecommended: "Die folgenden Einstellungen werden empfohlen" + applyTheseSettings: "Diese Einstellungen anwenden" + skipSettings: "Konfiguration überspringen" + settingsCompleted: "Einrichtung abgeschlossen!" + settingsCompleted_description: "Vielen Dank für deine Zeit. Jetzt, wo alles fertig ist, kannst du den Server sofort benutzen." + settingsCompleted_description2: "Detaillierte Servereinstellungen können über die „Systemsteuerung“ vorgenommen werden." + donationRequest: "Spendenaufruf" + _donationRequest: + text1: "Misskey ist eine freie Software, die von Freiwilligen entwickelt wird." + text2: "Wir würden uns über deine Unterstützung freuen, damit wir dieses Projekt auch in Zukunft weiterentwickeln können." + text3: "Für Unterstützer gibt es auch besondere Vorteile!" +_uploader: + editImage: "Bild bearbeiten" + compressedToX: "Komprimiert zu {x}" + savedXPercent: "{x}% gespart" + abortConfirm: "Einige Dateien wurden nicht hochgeladen. Möchtest du den Vorgang abbrechen?" + doneConfirm: "Einige Dateien wurden nicht hochgeladen. Möchtest du den Vorgang fortsetzen?" + maxFileSizeIsX: "Die maximale Dateigröße, die hochgeladen werden kann, beträgt {x}." + allowedTypes: "Hochladbare Dateitypen" + tip: "Die Datei ist noch nicht hochgeladen worden. In diesem Dialog kannst du die Datei vor dem Hochladen anzeigen, umbenennen, komprimieren und zuschneiden. Wenn du fertig bist, klicke auf „Hochladen“, um den Upload zu starten." +_clientPerformanceIssueTip: + makeSureDisabledAdBlocker: "Deaktiviere deinen Adblocker" + makeSureDisabledAdBlocker_description: "Adblocker können die Leistung beeinträchtigen; vergewissere dich, ob in deinem Betriebssystem, Browser oder deinen Add-ons Adblocker aktiviert sind." + makeSureDisabledCustomCss: "Benutzerdefiniertes CSS deaktivieren" + makeSureDisabledCustomCss_description: "Das Überschreiben von Stilen kann die Leistung beeinträchtigen. Stelle daher sicher, dass du kein benutzerdefiniertes CSS oder Erweiterungen aktiviert hast, die Stile überschreiben." + makeSureDisabledAddons: "Erweiterungen deaktivieren" + makeSureDisabledAddons_description: "Einige Erweiterungen können das Verhalten des Clients stören und die Leistung beeinträchtigen. Deaktiviere die Browser-Erweiterungen und prüfe, ob sich die Situation dadurch verbessert." +_clip: + tip: "Clips sind eine Funktion, mit der du Notizen gruppieren kannst." +_userLists: + tip: "Es können Listen mit beliebigen Benutzern erstellt werden. Die erstellte Liste kann als eigene Chronik angezeigt werden." +watermark: "Wasserzeichen" +defaultPreset: "Standard-Voreinstellungen" +_watermarkEditor: + tip: "Dem Bild kann ein Wasserzeichen, z. B. eine Quellenangabe, hinzugefügt werden." + quitWithoutSaveConfirm: "Nicht gespeicherte Änderungen verwerfen?" + driveFileTypeWarn: "Diese Datei wird nicht unterstützt" + driveFileTypeWarnDescription: "Bilddatei auswählen" + title: "Wasserzeichen bearbeiten" + cover: "Alles bedecken" + opacity: "Transparenz" + scale: "Größe" + text: "Text" + position: "Position" + type: "Art" + image: "Bilder" + advanced: "Fortgeschritten" + angle: "Winkel" + stripe: "Streifen" + stripeWidth: "Linienbreite" + stripeFrequency: "Linienanzahl" + polkadot: "Punktmuster" + polkadotMainDotOpacity: "Deckkraft des Hauptpunktes" + polkadotMainDotRadius: "Größe des Hauptpunktes" + polkadotSubDotOpacity: "Deckkraft des Unterpunktes" + polkadotSubDotRadius: "Größe des Unterpunktes" + polkadotSubDotDivisions: "Anzahl der Unterpunkte" +_imageEffector: + title: "Effekte" + addEffect: "Effekte hinzufügen" + discardChangesConfirm: "Änderungen verwerfen und beenden?" + _fxs: + chromaticAberration: "Chromatische Abweichung" + glitch: "Glitch" + mirror: "Spiegeln" + invert: "Farben umkehren" + grayscale: "Schwarzweiß" + colorAdjust: "Farbkorrektur" + colorClamp: "Farbkomprimierung" + colorClampAdvanced: "Farbkomprimierung (erweitert)" + distort: "Verzerrung" + stripe: "Streifen" + polkadot: "Punktmuster" + _fxProps: + angle: "Winkel" + scale: "Größe" + size: "Größe" + offset: "Position" + color: "Farbe" + opacity: "Transparenz" + lightness: "Erhellen" +drafts: "Entwurf" +_drafts: + select: "Entwurf auswählen" + cannotCreateDraftAnymore: "Die Anzahl der Entwürfe, die erstellt werden können, wurde überschritten." + cannotCreateDraft: "Mit diesem Inhalt kann kein Entwurf erstellt werden." + delete: "Entwurf löschen" + deleteAreYouSure: "Entwurf löschen?" + noDrafts: "Keine Entwürfe" + replyTo: "Antwort an {user}" + quoteOf: "Zitat von {user}s Notiz" + saveToDraft: "Als Entwurf speichern" + restoreFromDraft: "Aus Entwurf wiederherstellen" + restore: "Wiederherstellen" + listDrafts: "Liste der Entwürfe" +_qr: + showTabTitle: "Anzeigeart" + raw: "Text" diff --git a/locales/el-GR.yml b/locales/el-GR.yml index c8aff304d2..693e2b93c5 100644 --- a/locales/el-GR.yml +++ b/locales/el-GR.yml @@ -288,6 +288,10 @@ replies: "Απάντηση" renotes: "Κοινοποίηση σημειώματος" postForm: "Φόρμα δημοσίευσης" information: "Πληροφορίες" +widgets: "Μαραφέτια" +_imageEditing: + _vars: + filename: "Όνομα αρχείου" _chat: members: "Μέλη" home: "Κεντρικό" @@ -353,6 +357,7 @@ _visibility: home: "Κεντρικό" homeDescription: "Δημοσίευση στο κεντρικό χρονολόγιο μόνο" followers: "Ακολουθούν" + specified: "Απευθείας σημειώματα" _profile: name: "Όνομα" username: "Όνομα μέλους" @@ -395,6 +400,7 @@ _deck: antenna: "Αντένες" list: "Λίστα" mentions: "Επισημάνσεις" + direct: "Απευθείας σημειώματα" _webhookSettings: name: "Όνομα" _moderationLogTypes: @@ -403,3 +409,5 @@ _reversi: total: "Σύνολο" _search: searchScopeLocal: "Τοπικό" +_watermarkEditor: + image: "Εικόνες" diff --git a/locales/en-US.yml b/locales/en-US.yml index 533682fd4f..17268af1cb 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -81,8 +81,10 @@ import: "Import" export: "Export" files: "Files" download: "Download" -driveFileDeleteConfirm: "Do you want to remove the file \"{name}\"? Some content using this file will also be removed." +driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? All notes with this file attached will also be deleted." unfollowConfirm: "Are you sure you want to unfollow {name}?" +cancelFollowRequestConfirm: "Are you sure that you want to cancel your follow request to {name}?" +rejectFollowRequestConfirm: "Are you sure that you want to reject the follow request from {name}?" exportRequested: "You've requested an export. This may take a while. It will be added to your Drive once completed." importRequested: "You've requested an import. This may take a while." lists: "Lists" @@ -220,6 +222,7 @@ silenceThisInstance: "Silence this instance" mediaSilenceThisInstance: "Media-silence this server" operations: "Operations" software: "Software" +softwareName: "Software" version: "Version" metadata: "Metadata" withNFiles: "{n} file(s)" @@ -250,9 +253,9 @@ noUsers: "There are no users" editProfile: "Edit profile" noteDeleteConfirm: "Are you sure you want to delete this note?" pinLimitExceeded: "You cannot pin any more notes" -intro: "Installation of Misskey has been finished! Please create an admin user." done: "Done" processing: "Processing..." +preprocessing: "Preparing..." preview: "Preview" default: "Default" defaultValueIs: "Default: {value}" @@ -298,8 +301,10 @@ uploadFromUrl: "Upload from a URL" uploadFromUrlDescription: "URL of the file you want to upload" uploadFromUrlRequested: "Upload requested" uploadFromUrlMayTakeTime: "It may take some time until the upload is complete." +uploadNFiles: "Upload {n} files" explore: "Explore" messageRead: "Read" +readAllChatMessages: "Mark all messages as read" noMoreHistory: "There is no further history" startChat: "Start chat" nUsersRead: "read by {n}" @@ -326,11 +331,13 @@ dark: "Dark" lightThemes: "Light themes" darkThemes: "Dark themes" syncDeviceDarkMode: "Sync Dark Mode with your device settings" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" is turned on. Would you like to turn off synchronization and switch modes manually?" drive: "Drive" fileName: "Filename" selectFile: "Select a file" selectFiles: "Select files" selectFolder: "Select a folder" +unselectFolder: "Deselect folder" selectFolders: "Select folders" fileNotSelected: "No file selected" renameFile: "Rename file" @@ -343,6 +350,7 @@ addFile: "Add a file" showFile: "Show files" emptyDrive: "Your Drive is empty" emptyFolder: "This folder is empty" +dropHereToUpload: "Drop files here to upload" unableToDelete: "Unable to delete" inputNewFileName: "Enter a new filename" inputNewDescription: "Enter new alt text" @@ -575,8 +583,10 @@ showFixedPostForm: "Display the posting form at the top of the timeline" showFixedPostFormInChannel: "Display the posting form at the top of the timeline (Channels)" withRepliesByDefaultForNewlyFollowed: "Include replies by newly followed users in the timeline by default" newNoteRecived: "There are new notes" +newNote: "New Note" sounds: "Sounds" sound: "Sounds" +notificationSoundSettings: "Notification sound settings" listen: "Listen" none: "None" showInPage: "Show in page" @@ -768,6 +778,7 @@ lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", yo alwaysMarkSensitive: "Mark as sensitive by default" loadRawImages: "Load original images instead of showing thumbnails" disableShowingAnimatedImages: "Don't play animated images" +disableShowingAnimatedImages_caption: "If animated images do not play even if this setting is disabled, it may be due to browser or OS accessibility settings, power-saving settings, or similar factors." highlightSensitiveMedia: "Highlight sensitive media" verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification." notSet: "Not set" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "This is an experimental feature. Its functionality i developer: "Developer" makeExplorable: "Make account visible in \"Explore\"" makeExplorableDescription: "If you turn this off, your account will not show up in the \"Explore\" section." -showGapBetweenNotesInTimeline: "Show a gap between posts on the timeline" duplicate: "Duplicate" left: "Left" center: "Center" @@ -792,6 +802,7 @@ wide: "Wide" narrow: "Narrow" reloadToApplySetting: "This setting will only apply after a page reload. Reload now?" needReloadToApply: "A reload is required for this to be reflected." +needToRestartServerToApply: "A Misskey restart is required to reflect the change." showTitlebar: "Show title bar" clearCache: "Clear cache" onlineUsersCount: "{n} users are online" @@ -978,7 +989,8 @@ deleteAccount: "Delete account" document: "Documentation" numberOfPageCache: "Number of cached pages" numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device." -logoutConfirm: "Really log out?" +logoutConfirm: "Are you sure you want to log out?" +logoutWillClearClientData: "Logging out will erase the settings of the client from the browser. In order to be able to restore the settings upon logging in again, you must enable automatic backup of your settings." lastActiveDate: "Last used at" statusbar: "Status bar" pleaseSelect: "Select an option" @@ -997,6 +1009,7 @@ failedToUpload: "Upload failed" cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of it have been detected as potentially inappropriate." cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity." cannotUploadBecauseExceedsFileSizeLimit: "This file cannot be uploaded as it exceeds the file size limit." +cannotUploadBecauseUnallowedFileType: "Unable to upload due to unauthorized file type." beta: "Beta" enableAutoSensitive: "Automatic marking as sensitive" enableAutoSensitiveDescription: "Allows automatic detection and marking of sensitive media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide." @@ -1012,6 +1025,9 @@ pushNotificationAlreadySubscribed: "Push notifications are already enabled" pushNotificationNotSupported: "Your browser or instance does not support push notifications" sendPushNotificationReadMessage: "Delete push notifications once they have been read" sendPushNotificationReadMessageCaption: "This may increase the power consumption of your device." +pleaseAllowPushNotification: "Please enable push notifications in your browser" +browserPushNotificationDisabled: "Failed to acquire permission to send notifications" +browserPushNotificationDisabledDescription: "You do not have permission to send notifications from {serverName}. Please allow notifications in your browser settings and try again." windowMaximize: "Maximize" windowMinimize: "Minimize" windowRestore: "Restore" @@ -1048,6 +1064,7 @@ permissionDeniedError: "Operation denied" permissionDeniedErrorDescription: "This account does not have the permission to perform this action." preset: "Preset" selectFromPresets: "Choose from presets" +custom: "Custom" achievements: "Achievements" gotInvalidResponseError: "Invalid server response" gotInvalidResponseErrorDescription: "The server may be unreachable or undergoing maintenance. Please try again later." @@ -1086,6 +1103,7 @@ prohibitedWordsDescription2: "Using spaces will create AND expressions and surro hiddenTags: "Hidden hashtags" hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines." notesSearchNotAvailable: "Note search is unavailable." +usersSearchNotAvailable: "User search is not available." license: "License" unfavoriteConfirm: "Really remove from favorites?" myClips: "My clips" @@ -1160,6 +1178,7 @@ installed: "Installed" branding: "Branding" enableServerMachineStats: "Publish server hardware stats" enableIdenticonGeneration: "Enable user identicon generation" +showRoleBadgesOfRemoteUsers: "Display the role badges assigned to remote users" turnOffToImprovePerformance: "Turning this off can increase performance." createInviteCode: "Generate invite" createWithOptions: "Generate with options" @@ -1210,8 +1229,8 @@ showRepliesToOthersInTimeline: "Show replies to others in timeline" hideRepliesToOthersInTimeline: "Hide replies to others from timeline" showRepliesToOthersInTimelineAll: "Show replies to others from everyone you follow in timeline" hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you follow in timeline" -confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?" -confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?" +confirmShowRepliesAll: "Are you sure you want to show replies from everyone you follow in your timeline? This action is irreversible." +confirmHideRepliesAll: "Are you sure you want to hide replies from everyone you follow in your timeline? This action is irreversible." externalServices: "External Services" sourceCode: "Source code" sourceCodeIsNotYetProvided: "Source code is not yet available. Contact the administrator to fix this problem." @@ -1236,9 +1255,8 @@ showAvatarDecorations: "Show avatar decorations" releaseToRefresh: "Release to refresh" refreshing: "Refreshing..." pullDownToRefresh: "Pull down to refresh" -disableStreamingTimeline: "Disable real-time timeline updates" useGroupedNotifications: "Display grouped notifications" -signupPendingError: "There was a problem verifying the email address. The link may have expired." +emailVerificationFailedError: "A problem occurred while verifying your email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." doReaction: "Add reaction" code: "Code" @@ -1271,7 +1289,7 @@ notUsePleaseLeaveBlank: "Leave blank if not used" useTotp: "Enter the One-Time Password" useBackupCode: "Use the backup codes" launchApp: "Launch the app" -useNativeUIForVideoAudioPlayer: "Use UI of browser when play video and audio" +useNativeUIForVideoAudioPlayer: "Use UI of browser when play video and audio\n" keepOriginalFilename: "Keep original file name" keepOriginalFilenameDescription: "If you turn off this setting, files names will be replaced with random string automatically when you upload files." noDescription: "There is no explanation" @@ -1297,7 +1315,7 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification messageToFollower: "Message to followers" target: "Target" testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\nDo not use in a production environment." -prohibitedWordsForNameOfUser: "Prohibited words for user names" +prohibitedWordsForNameOfUser: "Prohibited words for usernames" prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction." yourNameContainsProhibitedWords: "Your name contains prohibited words" yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator." @@ -1308,6 +1326,8 @@ availableRoles: "Available roles" acknowledgeNotesAndEnable: "Turn on after understanding the precautions." federationSpecified: "This server is operated in a whitelist federation. Interacting with servers other than those designated by the administrator is not allowed." federationDisabled: "Federation is disabled on this server. You cannot interact with users on other servers." +draft: "Drafts" +draftsAndScheduledNotes: "Drafts and scheduled notes" confirmOnReact: "Confirm when reacting" reactAreYouSure: "Would you like to add a \"{emoji}\" reaction?" markAsSensitiveConfirm: "Do you want to set this media as sensitive?" @@ -1325,6 +1345,7 @@ restore: "Restore" syncBetweenDevices: "Sync between devices" preferenceSyncConflictTitle: "The configured value exists on the server." preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?" +preferenceSyncConflictChoiceMerge: "Merge" preferenceSyncConflictChoiceServer: "Configured value on server" preferenceSyncConflictChoiceDevice: "Configured value on device" preferenceSyncConflictChoiceCancel: "Cancel enabling sync" @@ -1334,6 +1355,8 @@ postForm: "Posting form" textCount: "Character count" information: "About" chat: "Chat" +directMessage: "Chat with user" +directMessage_short: "Message" migrateOldSettings: "Migrate old client settings" migrateOldSettings_description: "This should be done automatically but if for some reason the migration was not successful, you can trigger the migration process yourself manually. The current configuration information will be overwritten." compress: "Compress" @@ -1344,7 +1367,100 @@ embed: "Embed" settingsMigrating: "Settings are being migrated, please wait a moment... (You can also migrate manually later by going to Settings→Others→Migrate old settings)" readonly: "Read only" goToDeck: "Return to Deck" +federationJobs: "Federation Jobs" +driveAboutTip: "In Drive, a list of files you've uploaded in the past will be displayed.
\nYou can reuse these files when attaching them to notes, or you can upload files in advance to post later.
\nBe careful when deleting a file, as it will not be available in all places where it was used (such as notes, pages, avatars, banners, etc.).
\nYou can also create folders to organize your files." +scrollToClose: "Scroll to close" +advice: "Advice" +realtimeMode: "Real-time mode" +turnItOn: "Turn on" +turnItOff: "Turn off" +emojiMute: "Mute emoji" +emojiUnmute: "Unmute emoji" +muteX: "Mute {x}" +unmuteX: "Unmute {x}" +abort: "Abort" +tip: "Tips & Tricks" +redisplayAllTips: "Show all “Tips & Tricks” again" +hideAllTips: "Hide all \"Tips & Tricks\"" +defaultImageCompressionLevel: "Default image compression level" +defaultImageCompressionLevel_description: "Lower level preserves image quality but increases file size.
Higher level reduce file size, but reduce image quality." +defaultCompressionLevel: "Default compression level" +defaultCompressionLevel_description: "Lower compression preserves quality but increases file size.
Higher compression reduces file size but lowers quality." +inMinutes: "Minute(s)" +inDays: "Day(s)" +safeModeEnabled: "Safe mode is enabled" +pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled." +customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled." +themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes." +thankYouForTestingBeta: "Thank you for helping us test the beta version!" +createUserSpecifiedNote: "Create a direct note" +schedulePost: "Schedule note" +scheduleToPostOnX: "Scheduled to note on {x}" +scheduledToPostOnX: "Note is scheduled for {x}" +schedule: "Schedule" +scheduled: "Scheduled" +widgets: "Widgets" +deviceInfo: "Device information" +deviceInfoDescription: "When making technical inquiries, including the following information may help resolve the issue." +youAreAdmin: "You are admin" +frame: "Frame" +presets: "Preset" +zeroPadding: "Zero padding" +nothingToConfigure: "No configurable options available" +_imageEditing: + _vars: + caption: "File caption" + filename: "Filename" + filename_without_ext: "Filename without extension" + year: "Year of photography" + month: "Month of photogrphy" + day: "Date of photography" + hour: "Time the photo was taken (hour)" + minute: "Time the photo was taken (minute)" + second: "Time the photo was taken (second)" + camera_model: "Camera Name" + camera_lens_model: "Lens model" + camera_mm: "Focal length" + camera_mm_35: "Focal length (in 35 mm format)" + camera_f: "Aperture (f-number)" + camera_s: "Shutter speed" + camera_iso: "ISO" + gps_lat: "Latitude" + gps_long: "Longitude" +_imageFrameEditor: + title: "Edit frame" + tip: "You can decorate images by adding labels that include frames and metadata." + header: "Header" + footer: "Footer" + borderThickness: "Frame width" + labelThickness: "Label width" + labelScale: "Label scale" + centered: "Centered" + captionMain: "Caption (Big)" + captionSub: "Caption (Small)" + availableVariables: "Supported variables" + withQrCode: "QR Code" + backgroundColor: "Background color" + textColor: "Text color" + font: "Font" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "Discard unsaved changes?" + failedToLoadImage: "Failed to load image" +_compression: + _quality: + high: "High quality" + medium: "Medium quality" + low: "Low quality" + _size: + large: "Large size" + medium: "Medium size" + small: "Small size" +_order: + newest: "Newest First" + oldest: "Oldest First" _chat: + messages: "Messages" noMessagesYet: "No messages yet" newMessage: "New message" individualChat: "Private Chat" @@ -1373,10 +1489,12 @@ _chat: muteThisRoom: "Mute room" deleteRoom: "Delete room" chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account." - chatIsReadOnlyForThisAccountOrServer: "Chat is read-only on this instance or this account. You cannot write new messages or create/join chat rooms." + chatIsReadOnlyForThisAccountOrServer: "Chat is read-only on this server or this account. You cannot write new messages or create/join chat rooms." chatNotAvailableInOtherAccount: "The chat function is disabled for the other user." cannotChatWithTheUser: "Cannot start a chat with this user" cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat." + youAreNotAMemberOfThisRoomButInvited: "You are not a participant in this room, but you have received an invitation. Please accept the invitation to join." + doYouAcceptInvitation: "Do you accept the invitation?" chatWithThisUser: "Chat with user" thisUserAllowsChatOnlyFromFollowers: "This user accepts chats from followers only." thisUserAllowsChatOnlyFromFollowing: "This user accepts chats only from users they follow." @@ -1416,10 +1534,26 @@ _settings: makeEveryTextElementsSelectable: "Make all text elements selectable" makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations." useStickyIcons: "Make icons follow while scrolling" + enableHighQualityImagePlaceholders: "Display placeholders for high quality images" + uiAnimations: "UI Animations" showNavbarSubButtons: "Show sub-buttons on the navigation bar" ifOn: "When turned on" ifOff: "When turned off" enableSyncThemesBetweenDevices: "Synchronize installed themes across devices" + enablePullToRefresh: "Pull to Refresh" + enablePullToRefresh_description: "When using a mouse, drag while pressing in the scroll wheel." + realtimeMode_description: "Establishes a connection with the server and updates content in real time. This may increase traffic and memory consumption." + contentsUpdateFrequency: "Frequency of content retrieval" + contentsUpdateFrequency_description: "The higher the value the more the content updates but it lowers the performance and increases the traffic and memory consumption." + contentsUpdateFrequency_description2: "When real-time mode is on, content is updated in real time regardless of this setting." + showUrlPreview: "Show URL preview" + showAvailableReactionsFirstInNote: "Show available reactions at the top." + showPageTabBarBottom: "Show page tab bar at the bottom" + emojiPaletteBanner: "You can register presets as palettes to display prominently in the emoji picker or customize the appearance of the picker." + enableAnimatedImages: "Enable animated images" + settingsPersistence_title: "Persistence of Settings" + settingsPersistence_description1: "Enabling setting persistence prevents configuration information from being lost." + settingsPersistence_description2: "It may not be possible to enable this depending on the environment." _chat: showSenderName: "Show sender's name" sendOnEnter: "Press Enter to send" @@ -1427,6 +1561,9 @@ _preferencesProfile: profileName: "Profile name" profileNameDescription: "Set a name that identifies this device." profileNameDescription2: "Example: \"Main PC\", \"Smartphone\"" + manageProfiles: "Manage Profiles" + shareSameProfileBetweenDevicesIsNotRecommended: "We do not recommend sharing the same profile across multiple devices." + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "If there are settings you wish to synchronize across multiple devices, enable the “Synchronize across multiple devices” option individually for each device." _preferencesBackup: autoBackup: "Auto backup" restoreFromBackup: "Restore from backup" @@ -1436,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "A profile name must be set to enable auto backup." autoPreferencesBackupIsNotEnabledForThisDevice: "Settings auto backup is not enabled on this device." backupFound: "Settings backup is found" + forceBackup: "Force a backup of settings" _accountSettings: requireSigninToViewContents: "Require sign-in to view contents" requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information." @@ -1458,13 +1596,14 @@ _abuseUserReport: resolveTutorial: "If the report's content is legitimate, select \"Accept\" to mark it as resolved.\nIf the report's content is illegitimate, select \"Reject\" to ignore it." _delivery: status: "Delivery status" - stop: "Suspended" + stop: "Suspend" resume: "Delivery resume" _type: none: "Publishing" manuallySuspended: "Manually suspended" goneSuspended: "Server is suspended due to server deletion" autoSuspendedForNotResponding: "Server is suspended due to no responding" + softwareSuspended: "Suspended as this software is no longer being distributed to" _bubbleGame: howToPlay: "How to play" hold: "Hold" @@ -1591,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "Fallback to database" fanoutTimelineDbFallbackDescription: "When enabled, the timeline will fall back to the database for additional queries if the timeline is not cached. Disabling it further reduces the server load by eliminating the fallback process, but limits the range of timelines that can be retrieved." reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase." + remoteNotesCleaning: "Automatic cleanup of remote notes" + remoteNotesCleaning_description: "When enabled, unused and outdated remote notes will be periodically cleaned up to prevent database bloat." + remoteNotesCleaningMaxProcessingDuration: "Maximum cleanup processing time" + remoteNotesCleaningExpiryDaysForEachNotes: "Minimum days to retain notes" inquiryUrl: "Inquiry URL" inquiryUrlDescription: "Specify a URL for the inquiry form to the server maintainer or a web page for the contact information." openRegistration: "Make the account creation open" openRegistrationWarning: "Opening registration carries risks. It is recommended to only enable it if you have a system in place to continuously monitor the server and respond immediately in case of any issues." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "If no moderator activity is detected for a while, this setting will be automatically turned off to prevent spam." + deliverSuspendedSoftware: "Suspended Software" + deliverSuspendedSoftwareDescription: "You can specify a range of names and versions of the server's software to stop delivery for vulnerability or other reasons. This version information is provided by the server and is not guaranteed to be reliable. A semver range specification can be used to specify the version, but specifying >= 2024.3.1 will not include custom versions such as 2024.3.1-custom.0, so it is recommended that a prerelease specification be used, such as >= 2024.3.1-0" + singleUserMode: "Single user mode" + singleUserMode_description: "If you are the only user of this server, enabling this mode will optimize its performance." + signToActivityPubGet: "Sign ActivityPub GET requests" + signToActivityPubGet_description: "Normally, this should be enabled. Disabling it may improve issues related to federation, but on the other hand it could disable federation towards some other servers." + proxyRemoteFiles: "Proxy remote files" + proxyRemoteFiles_description: "When enabled, the server will proxy and serve remote files. This is useful for generating image thumbnails and protecting user privacy." + allowExternalApRedirect: "Allow redirects for queries via ActivityPub" + allowExternalApRedirect_description: "If enabled, other servers can query third-party content through this server but this may result in content spoofing." + userGeneratedContentsVisibilityForVisitor: "Visibility of user-generated content to guests" + userGeneratedContentsVisibilityForVisitor_description: "This is useful for preventing problems caused by inappropriate remote content that is not well moderated from being unintentionally published on the Internet via your own server." + userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server." + restartServerSetupWizardConfirm_title: "Restart server setup wizard?" + restartServerSetupWizardConfirm_text: "Some current settings will be reset." + entrancePageStyle: "Entrance page style" + showTimelineForVisitor: "Show timeline" + showActivitiesForVisitor: "Show activities" + _userGeneratedContentsVisibilityForVisitor: + all: "Everything is public" + localOnly: "Only local content is published, remote content is kept private" + none: "Everything is private" _accountMigration: moveFrom: "Migrate another account to this one" moveFromSub: "Create alias to another account" @@ -1913,6 +2078,8 @@ _role: canManageCustomEmojis: "Can manage custom emojis" canManageAvatarDecorations: "Manage avatar decorations" driveCapacity: "Drive capacity" + maxFileSize: "Upload-able max file size" + maxFileSize_caption: "Reverse proxies, CDNs, and other front-end components may have their own configuration settings." alwaysMarkNsfw: "Always mark files as NSFW" canUpdateBioMedia: "Can edit an icon or a banner image" pinMax: "Maximum number of pinned notes" @@ -1927,14 +2094,21 @@ _role: descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. " canHideAds: "Can hide ads" canSearchNotes: "Usage of note search" + canSearchUsers: "User search" canUseTranslator: "Translator usage" - avatarDecorationLimit: "Maximum number of avatar decorations that can be applied" - canImportAntennas: "Allow importing antennas" - canImportBlocking: "Allow importing blocking" - canImportFollowing: "Allow importing following" - canImportMuting: "Allow importing muting" - canImportUserLists: "Allow importing lists" - chatAvailability: "Allow Chat" + avatarDecorationLimit: "Maximum number of avatar decorations" + canImportAntennas: "Can import antennas" + canImportBlocking: "Can import blocking" + canImportFollowing: "Can import following" + canImportMuting: "Can import muting" + canImportUserLists: "Can import lists" + chatAvailability: "Chat" + uploadableFileTypes: "Uploadable file types" + uploadableFileTypes_caption: "Specifies the allowed MIME/file types. Multiple MIME types can be specified by separating them with a new line, and wildcards can be specified with an asterisk (*). (e.g., image/*)" + uploadableFileTypes_caption2: "Some files types might fail to be detected. To allow such files, add {x} to the specification." + noteDraftLimit: "Number of possible drafts of server notes" + scheduledNoteLimit: "Maximum number of simultaneous scheduled notes" + watermarkAvailable: "Watermark function" _condition: roleAssignedTo: "Assigned to manual roles" isLocal: "Local user" @@ -2094,6 +2268,7 @@ _theme: install: "Install a theme" manage: "Manage themes" code: "Theme code" + copyThemeCode: "Copy theme code" description: "Description" installed: "{name} has been installed" installedThemes: "Installed themes" @@ -2152,7 +2327,6 @@ _theme: buttonBg: "Button background" buttonHoverBg: "Button background (Hover)" inputBorder: "Input field border" - driveFolderBg: "Drive folder background" badge: "Badge" messageBg: "Chat background" fgHighlighted: "Highlighted Text" @@ -2194,6 +2368,7 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" + month: "Month(s)" _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" @@ -2266,7 +2441,7 @@ _permissions: "read:admin:index-stats": "View database index stats" "read:admin:table-stats": "View database table stats" "read:admin:user-ips": "View user IP addresses" - "read:admin:meta": "View instance metadata" + "read:admin:meta": "View server metadata" "write:admin:reset-password": "Reset user password" "write:admin:resolve-abuse-user-report": "Resolve user report" "write:admin:send-email": "Send email" @@ -2277,7 +2452,7 @@ _permissions: "write:admin:unset-user-avatar": "Remove user avatar" "write:admin:unset-user-banner": "Remove user banner" "write:admin:unsuspend-user": "Unsuspend user" - "write:admin:meta": "Manage instance metadata" + "write:admin:meta": "Manage server metadata" "write:admin:user-note": "Manage moderation note" "write:admin:roles": "Manage roles" "read:admin:roles": "View roles" @@ -2323,6 +2498,7 @@ _auth: scopeUser: "Operate as the following user" pleaseLogin: "Please log in to authorize applications." byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL" + alreadyAuthorized: "This application already has access permission." _antennaSources: all: "All notes" homeTimeline: "Notes from followed users" @@ -2368,7 +2544,45 @@ _widgets: chooseList: "Select a list" clicker: "Clicker" birthdayFollowings: "Today's Birthdays" - chat: "Chat" + chat: "Chat with user" +_widgetOptions: + showHeader: "Show header" + transparent: "Make background transparent" + height: "Height" + _button: + colored: "Colored" + _clock: + size: "Size" + thickness: "Needle thickness" + thicknessThin: "Thin" + thicknessMedium: "Normal" + thicknessThick: "Thick" + graduations: "Dial markings" + graduationDots: "Dot" + graduationArabic: "Arabic numbers" + fadeGraduations: "Fade the scale" + sAnimation: "Second hand animation" + sAnimationElastic: "Real" + sAnimationEaseOut: "Smooth" + twentyFour: "24 Hour Format" + labelTime: "Time" + labelTz: "Timezone" + labelTimeAndTz: "Time and time zone" + timezone: "Timezone" + showMs: "Show Miliseconds" + showLabel: "Show Label" + _jobQueue: + sound: "Play Sounds" + _rss: + url: "RSS Feed Url" + refreshIntervalSec: "Update interval (in seconds)" + maxEntries: "Maximum number of items to display" + _rssTicker: + shuffle: "Random display order" + duration: "Banner scroll speed (in seconds)" + reverse: "Scroll in the opposite direction" + _birthdayFollowings: + period: "Duration" _cw: hide: "Hide" show: "Show content" @@ -2408,9 +2622,25 @@ _visibility: disableFederation: "Defederate" disableFederationDescription: "Don't transmit to other instances" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "There are files that have not been uploaded, do you want to discard them and close the form?" + uploaderTip: "The file has not yet been uploaded. From the file menu, you can rename, crop images, watermark and compress or uncompress the file. Files are automatically uploaded when you publish a note." replyPlaceholder: "Reply to this note..." quotePlaceholder: "Quote this note..." channelPlaceholder: "Post to a channel..." + showHowToUse: "Show how to use this form" + _howToUse: + content_title: "Body" + content_description: "Enter the content you wish to post here." + toolbar_title: "Toolbars" + toolbar_description: "You can attach files or poll, add annotations or hashtags, and insert emojis or mentions." + account_title: "Account menu" + account_description: "You can switch between accounts for posting, or view a list of drafts and scheduled posts saved to your account." + visibility_title: "Visibility" + visibility_description: "You can configure the visibility of your notes." + menu_title: "Menu" + menu_description: "You can save current content to drafts, schedule posts, set reactions, and perform other actions." + submit_title: "Post button" + submit_description: "Post your notes by pressing this button. You can also post using Ctrl + Enter / Cmd + Enter." _placeholders: a: "What are you up to?" b: "What's happening around you?" @@ -2556,6 +2786,8 @@ _notification: youReceivedFollowRequest: "You've received a follow request" yourFollowRequestAccepted: "Your follow request was accepted" pollEnded: "Poll results have become available" + scheduledNotePosted: "Scheduled note has been posted" + scheduledNotePostFailed: "Failed to post scheduled note" newNote: "New note" unreadAntennaNote: "Antenna {name}" roleAssigned: "Role given" @@ -2585,6 +2817,8 @@ _notification: quote: "Quotes" reaction: "Reactions" pollEnded: "Polls ending" + scheduledNotePosted: "Scheduled note was successful" + scheduledNotePostFailed: "Scheduled note failed" receiveFollowRequest: "Received follow requests" followRequestAccepted: "Accepted follow requests" roleAssigned: "Role given" @@ -2624,6 +2858,14 @@ _deck: usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" flexible: "Auto-adjust width" enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices" + showHowToUse: "" + _howToUse: + addColumn_title: "Add column" + addColumn_description: "You can select and add column types." + settings_title: "UI Settings" + settings_description: "You can configure detailed settings for the deck UI." + switchProfile_title: "Profile Switching" + switchProfile_description: "You can save UI layouts as profiles and switch between them at any time." _columns: main: "Main" widgets: "Widgets" @@ -2635,7 +2877,7 @@ _deck: mentions: "Mentions" direct: "Direct notes" roleTimeline: "Role Timeline" - chat: "Chat" + chat: "Chat with user" _dialog: charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}." charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}." @@ -2684,6 +2926,8 @@ _abuseReport: notifiedWebhook: "Webhook to use" deleteConfirm: "Are you sure that you want to delete the notification recipient?" _moderationLogTypes: + clearQueue: "Clear queue" + promoteQueue: "Promote queue" createRole: "Role created" deleteRole: "Role deleted" updateRole: "Role updated" @@ -2707,7 +2951,7 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" - updateRemoteInstanceNote: "Moderation note updated for remote instance." + updateRemoteInstanceNote: "Updated moderation note for remote servers" markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" resolveAbuseReport: "Report resolved" @@ -2741,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "Uploaded at" attachedNotes: "Attached notes" + usage: "Used" thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file." _externalResourceInstaller: title: "Install from external site" @@ -2788,9 +3033,12 @@ _dataSaver: _avatar: title: "Avatar image" description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic." - _urlPreview: - title: "URL preview thumbnails" + _urlPreviewThumbnail: + title: "Hide URL preview thumbnails" description: "URL preview thumbnail images will no longer be loaded." + _disableUrlPreview: + title: "Disable URL preview" + description: "Disables the URL preview function. Unlike thumbnail images, this function reduces the loading of the linked information itself." _code: title: "Code highlighting" description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data." @@ -2848,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "URL preview settings" enable: "Enable URL preview" + allowRedirect: "Allow URL preview redirection" + allowRedirectDescription: "If a URL has a redirection set, you can enable this feature to follow the redirection and display a preview of the redirected content. Disabling this will save server resources, but redirected content will not be displayed." timeout: "Time out when getting preview (ms)" timeoutDescription: "If it takes longer than this value to get the preview, the preview won’t be generated." maximumContentLength: "Maximum Content-Length (bytes)" @@ -2909,22 +3159,18 @@ _customEmojisManager: markAsDeleteTargetRanges: "Mark rows in the selection as a target to delete" alertUpdateEmojisNothingDescription: "There are no updated Emojis." alertDeleteEmojisNothingDescription: "There are no Emojis to be deleted." - confirmMovePage: "" + confirmMovePage: "Would you like to move pages?" confirmChangeView: "" confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?" confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?" confirmResetDescription: "" confirmMovePageDesciption: "Changes have been made to the Emojis on this page.\nIf you leave the page without saving, all changes made on this page will be discarded." - dialogSelectRoleTitle: "Search by roll set in Emojis" + dialogSelectRoleTitle: "Search by role set in Emojis" _register: uploadSettingTitle: "Upload settings" uploadSettingDescription: "On this screen, you can configure the behavior when uploading Emojis." directoryToCategoryLabel: "Enter the directory name in the \"category\" field" directoryToCategoryCaption: "When you drag and drop a directory, enter the directory name in the \"category\" field." - emojiInputAreaCaption: "Select the Emojis you wish to register using one of the methods." - emojiInputAreaList1: "Drag and drop image files or a directory into this frame" - emojiInputAreaList2: "Click this link to select from your computer" - emojiInputAreaList3: "Click this link to select from the drive" confirmRegisterEmojisDescription: "Register the Emojis from the list as new custom Emojis. Are you sure to continue? (To avoid overload, only {count} Emoji(s) can be registered in a single operation)" confirmClearEmojisDescription: "Discard the edits and clear the Emojis from the list. Are you sure to continue?" confirmUploadEmojisDescription: "Upload the dragged and dropped {count} file(s) to the drive. Are you sure to continue?" @@ -2992,6 +3238,7 @@ _bootErrors: otherOption1: "Delete client settings and cache" otherOption2: "Start the simple client" otherOption3: "Launch the repair tool" + otherOption4: "Launch Misskey in safe mode" _search: searchScopeAll: "All" searchScopeLocal: "Local" @@ -3000,3 +3247,196 @@ _search: pleaseEnterServerHost: "Enter the server host" pleaseSelectUser: "Select user" serverHostPlaceholder: "Example: misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey installation is now complete!" + firstCreateAccount: "To begin, create an administrator account." + accountCreated: "Administrator account has been created!" + serverSetting: "Server Settings" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "This wizard makes it easier to configure the server settings." + settingsYouMakeHereCanBeChangedLater: "The settings that were changed via this wizard can be adjusted later." + howWillYouUseMisskey: "How will you use Misskey?" + _use: + single: "Single User server" + single_description: "Use it alone as your own server." + single_youCanCreateMultipleAccounts: "Multiple accounts can be created as needed, even when operated as a single user server." + group: "Group server" + group_description: "Invite other trusted users to use it with more than one user." + open: "Public server" + open_description: "Allow anyone to register." + openServerAdvice: "Accepting a large number of unknown users involves risk. We recommend that you operate with a reliable moderation system to handle any problems." + openServerAntiSpamAdvice: "To prevent your server from becoming a stepping stone for spam, you should also pay close attention to security by enabling anti-bot functions such as reCAPTCHA." + howManyUsersDoYouExpect: "How many users do you expect?" + _scale: + small: "Less than 100 (small scale)" + medium: "More than 100 and less than 1000 users (medium size)" + large: "More than 1000 (Large scale)" + largeScaleServerAdvice: "Large servers may require advanced infrastructure knowledge, such as load balancing and database replication." + doYouConnectToFediverse: "Do you want to connect to the Fediverse?" + doYouConnectToFediverse_description1: "When connected to a network of distributed servers (Fediverse) content can be exchanged with other servers." + doYouConnectToFediverse_description2: "Connecting with the Fediverse is also called \"federation\"" + youCanConfigureMoreFederationSettingsLater: "Advanced settings such as specifying federated servers can be configured later." + remoteContentsCleaning: "Automatic cleanup of received contents" + remoteContentsCleaning_description: "Federation may result in a continuous inflow of content. Enabling automatic cleanup will remove outdated and unreferenced content from the server to save storage." + adminInfo: "Administrator information" + adminInfo_description: "Sets the administrator information used to receive inquiries." + adminInfo_mustBeFilled: "Must be entered if public server or federation is on." + followingSettingsAreRecommended: "The following settings are recommended" + applyTheseSettings: "Apply these settings" + skipSettings: "Skip settings" + settingsCompleted: "Setup is now complete!" + settingsCompleted_description: "Thank you for your time. Now that everything is ready, you can start using the server right away." + settingsCompleted_description2: "The server settings can be changed from the “Control Panel”" + donationRequest: "Donation Request" + _donationRequest: + text1: "Misskey is a free software developed by volunteers." + text2: "We would appreciate your support so that we can continue to develop this software further into the future." + text3: "There are also special benefits for supporters!" +_uploader: + editImage: "Edit Image" + compressedToX: "Compressed to {x}" + savedXPercent: "Saving {x}%" + abortConfirm: "Some files have not been uploaded, do you want to abort?" + doneConfirm: "Some files have not been uploaded, do you want to continue anyway?" + maxFileSizeIsX: "The maximum file size that can be uploaded is {x}" + allowedTypes: "Uploadable file types" + tip: "The file has not yet been uploaded so this dialog allows you to confirm, rename, compress, and crop the file before uploading. When ready, you can start uploading by pressing the “Upload” button." +_clientPerformanceIssueTip: + title: "Performance tips" + makeSureDisabledAdBlocker: "Disable your adblocker" + makeSureDisabledAdBlocker_description: "Adblockers can affect performance, please make sure that adblockers are not enabled by your system or browser features/extensions." + makeSureDisabledCustomCss: "Disable custom CSS" + makeSureDisabledCustomCss_description: "Overriding styles can affect performance. Please make sure that custom CSS or extensions that override styles are not enabled." + makeSureDisabledAddons: "Disable extensions" + makeSureDisabledAddons_description: "Some extensions may interfere with client behavior and affect performance. Please disable your browser extensions and see if this improves the situation." +_clip: + tip: "Clip is a feature that allows you to organize your notes." +_userLists: + tip: "Lists can contain any user you specify when creating, the created list can then be displayed as a timeline showing only the specified users." +watermark: "Watermark" +defaultPreset: "Default Preset" +_watermarkEditor: + tip: "A watermark, such as credit information, can be added to the image." + quitWithoutSaveConfirm: "Discard unsaved changes?" + driveFileTypeWarn: "This file is not supported" + driveFileTypeWarnDescription: "Choose an image file" + title: "Edit Watermark" + cover: "Cover everything" + repeat: "spread all over" + preserveBoundingRect: "Adjust to prevent overflow when rotating" + opacity: "Opacity" + scale: "Size" + text: "Text" + qr: "QR Code" + position: "Position" + margin: "Margin" + type: "Type" + image: "Images" + advanced: "Advanced" + angle: "Angle" + stripe: "Stripes" + stripeWidth: "Line width" + stripeFrequency: "Lines count" + polkadot: "Polkadot" + checker: "Checker" + polkadotMainDotOpacity: "Opacity of the main dot" + polkadotMainDotRadius: "Size of the main dot" + polkadotSubDotOpacity: "Opacity of the secondary dot" + polkadotSubDotRadius: "Size of the secondary dot" + polkadotSubDotDivisions: "Number of sub-dots." + leaveBlankToAccountUrl: "Leave blank to use account URL" + failedToLoadImage: "Failed to load image" +_imageEffector: + title: "Effects" + addEffect: "Add Effects" + discardChangesConfirm: "Are you sure you want to leave? You have unsaved changes." + failedToLoadImage: "Failed to load image" + _fxs: + chromaticAberration: "Chromatic Aberration" + glitch: "Glitch" + mirror: "Mirror" + invert: "Invert Colors" + grayscale: "Grayscale" + blur: "Blur" + pixelate: "Pixelate" + colorAdjust: "Color Correction" + colorClamp: "Color Compression" + colorClampAdvanced: "Color Compression (Advanced)" + distort: "Distortion" + threshold: "Binarize" + zoomLines: "Saturated lines" + stripe: "Stripes" + polkadot: "Polkadot" + checker: "Checker" + blockNoise: "Block Noise" + tearing: "Tearing" + fill: "Fill" + _fxProps: + angle: "Angle" + scale: "Size" + size: "Size" + radius: "Radius" + samples: "Sample count" + offset: "Position" + color: "Color" + opacity: "Opacity" + normalize: "Normalize" + amount: "Amount" + lightness: "Lighten" + contrast: "Contrast" + hue: "Hue" + brightness: "Brightness" + saturation: "Saturation" + max: "Maximum" + min: "Minimum" + direction: "Direction" + phase: "Phase" + frequency: "Frequency" + strength: "Strength" + glitchChannelShift: "Channel shift" + seed: "Seed value" + redComponent: "Red component" + greenComponent: "Green component" + blueComponent: "Blue component" + threshold: "Threshold" + centerX: "Center X" + centerY: "Center Y" + zoomLinesSmoothing: "Smoothing" + zoomLinesSmoothingDescription: "Smoothing and zoom line width cannot be used together." + zoomLinesThreshold: "Zoom line width" + zoomLinesMaskSize: "Center diameter" + zoomLinesBlack: "Make black" + circle: "Circular" +drafts: "Drafts" +_drafts: + select: "Select Draft" + cannotCreateDraftAnymore: "The number of drafts that can be created has been exceeded." + cannotCreateDraft: "You cannot create a draft with this content." + delete: "Delete Draft" + deleteAreYouSure: "Delete draft?" + noDrafts: "No drafts" + replyTo: "Reply to {user}" + quoteOf: "Citation to {user}'s note" + postTo: "Posting to {channel}" + saveToDraft: "Save to Draft" + restoreFromDraft: "Restore from Draft" + restore: "Restore" + listDrafts: "List of Drafts" + schedule: "Schedule note" + listScheduledNotes: "Scheduled notes list" + cancelSchedule: "Cancel schedule" +qr: "QR Code" +_qr: + showTabTitle: "Display" + readTabTitle: "Scan" + shareTitle: "{name} {acct}" + shareText: "Follow me on the Fediverse!" + chooseCamera: "Choose camera" + cannotToggleFlash: "Unable to toggle flashlight" + turnOnFlash: "Turn on flashlight" + turnOffFlash: "Turn off flashlight" + startQr: "Resume QR code reader" + stopQr: "Stop QR code reader" + noQrCodeFound: "No QR code found" + scanFile: "Scan image from device" + raw: "Text" + mfm: "MFM" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 713478b67e..a243e678c2 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -5,11 +5,11 @@ introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentra poweredByMisskeyDescription: "{name} es uno de los servicios (también llamado instancia) que usa la plataforma de código abierto Misskey" monthAndDay: "{day}/{month}" search: "Buscar" -reset: "Reiniciar" +reset: "Restablecer" notifications: "Notificaciones" username: "Nombre de usuario" password: "Contraseña" -initialPasswordForSetup: "Contraseña para iniciar la inicialización" +initialPasswordForSetup: "Contraseña de configuración inicial" initialPasswordIsIncorrect: "La contraseña para iniciar la configuración inicial es incorrecta." initialPasswordForSetupDescription: "Si ha instalado Misskey usted mismo, utilice la contraseña introducida en el archivo de configuración.\nSi utiliza un servicio de alojamiento de Misskey o similar, utilice la contraseña proporcionada.\nSi no ha establecido una contraseña, déjela en blanco para continuar." forgotPassword: "Olvidé mi contraseña" @@ -43,7 +43,7 @@ favorite: "Añadir a favoritos" favorites: "Favoritos" unfavorite: "Quitar de favoritos" favorited: "Añadido a favoritos." -alreadyFavorited: "Ya había sido añadido a favoritos" +alreadyFavorited: "Ya añadido a favoritos." cantFavorite: "No se puede añadir a favoritos." pin: "Fijar al perfil" unpin: "Desfijar" @@ -61,11 +61,11 @@ copyRSS: "Copiar RSS" copyUsername: "Copiar nombre de usuario" copyUserId: "Copiar ID del usuario" copyNoteId: "Copiar ID de la nota" -copyFileId: "Copiar ID del archivo" +copyFileId: "Copiar ID de archivo" copyFolderId: "Copiar ID de carpeta" copyProfileUrl: "Copiar la URL del perfil" searchUser: "Buscar un usuario" -searchThisUsersNotes: "" +searchThisUsersNotes: "Buscar en las notas de este usuario" reply: "Responder" loadMore: "Ver más" showMore: "Ver más" @@ -83,11 +83,13 @@ files: "Archivos" download: "Descargar" driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas" unfollowConfirm: "¿Desea dejar de seguir a {name}?" -exportRequested: "Se ha solicitado la exportación. Puede tomar un tiempo. Cuando termine la exportación, se añadirá en el drive" -importRequested: "Se ha solicitado la importación. Puede tomar un tiempo." +cancelFollowRequestConfirm: "¿Desea cancelar su solicitud de seguimiento a {name}?" +rejectFollowRequestConfirm: "¿Desea rechazar la solicitud de seguimiento de {name}?" +exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive" +importRequested: "Has solicitado la importación. Puede llevar un tiempo." lists: "Listas" -noLists: "No tiene listas" -note: "Notas" +noLists: "No tienes ninguna lista" +note: "Nota" notes: "Notas" following: "Siguiendo" followers: "Seguidores" @@ -99,9 +101,9 @@ somethingHappened: "Ocurrió un error" retry: "Reintentar" pageLoadError: "Error al leer la página" pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde." -serverIsDead: "No hay respuesta del servidor. Espere un momento y vuelva a intentarlo." -youShouldUpgradeClient: "Para ver esta página, por favor refrezca el navegador y utiliza una versión más reciente del cliente." -enterListName: "Ingrese nombre de lista" +serverIsDead: "No hay respuesta del servidor. Espera un momento y vuelve a intentarlo." +youShouldUpgradeClient: "Para ver esta página, recarga el navegador para actualizar el cliente." +enterListName: "Introduce un nombre para la lista" privacy: "Privacidad" makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento" defaultNoteVisibility: "Visibilidad por defecto" @@ -125,37 +127,37 @@ renoteToOtherChannel: "Renotar a otro canal" pinnedNote: "Nota fijada" pinned: "Fijar al perfil" you: "Tú" -clickToShow: "Click para ver" -sensitive: "Marcado como sensible" +clickToShow: "Haz clic para verlo" +sensitive: "Marcado como sensible (NSFW)" add: "Agregar" reaction: "Reacción" -reactions: "Reacción" +reactions: "Reacciones" emojiPicker: "Selector de emojis" pinnedEmojisForReactionSettingDescription: "Puedes seleccionar reacciones para fijarlos en el selector" pinnedEmojisSettingDescription: "Puedes seleccionar emojis para fijarlos en el selector" emojiPickerDisplay: "Mostrar el selector de emojis" -overwriteFromPinnedEmojisForReaction: "Sobreescribir las reacciones fijadas" -overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados" -reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir." +overwriteFromPinnedEmojisForReaction: "Sobreescribir los ajustes de reacciones" +overwriteFromPinnedEmojis: "Sobreescribir los ajustes generales" +reactionSettingDescription2: "Arrastra para reordenar, click para borrar, pulsa \"+\" para añadir." rememberNoteVisibility: "Recordar visibilidad" attachCancel: "Quitar adjunto" -deleteFile: "Archivo eliminado" +deleteFile: "Eliminar archivo" markAsSensitive: "Marcar como sensible" -unmarkAsSensitive: "Desmarcar como sensible" -enterFileName: "Ingrese el nombre del archivo" +unmarkAsSensitive: "No marcar como sensible" +enterFileName: "Introduce el nombre del archivo" mute: "Silenciar" unmute: "Dejar de silenciar" renoteMute: "Silenciar renota" renoteUnmute: "Desilenciar renota" block: "Bloquear" -unblock: "Dejar de bloquear" +unblock: "Desbloquear" suspend: "Suspender" unsuspend: "Dejar de suspender" -blockConfirm: "¿Quiere bloquear esta cuenta?" -unblockConfirm: "¿Quiere dejar de bloquear esta cuenta?" -suspendConfirm: "¿Quiere suspender esta cuenta?" -unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?" -selectList: "Seleccione una lista" +blockConfirm: "¿Quieres bloquear esta cuenta?" +unblockConfirm: "¿Quieres desbloquear esta cuenta?" +suspendConfirm: "¿Quieres suspender esta cuenta?" +unsuspendConfirm: "¿Quieres dejar de suspender esta cuenta?" +selectList: "Selecciona una lista" editList: "Editar lista" selectChannel: "Seleccionar canal" selectAntenna: "Seleccionar antena" @@ -163,55 +165,55 @@ editAntenna: "Editar antena" createAntenna: "Crear una antena" selectWidget: "Seleccionar widget" editWidgets: "Editar widgets" -editWidgetsExit: "Terminar edición" +editWidgetsExit: "Hecho" customEmojis: "Emojis personalizados" emoji: "Emoji" -emojis: "Emoji" +emojis: "Emojis" emojiName: "Nombre del emoji" -emojiUrl: "URL de la imágen del emoji" -addEmoji: "Agregar emoji" -settingGuide: "Configuración sugerida" -cacheRemoteFiles: "Mantener en cache los archivos remotos" -cacheRemoteFilesDescription: "Si desactiva esta configuración, Los archivos remotos se cargarán desde el link directo sin usar la caché. Con eso se puede ahorrar almacenamiento del servidor, pero eso aumentará el tráfico al no crear miniaturas." +emojiUrl: "URL del emoji" +addEmoji: "Añadir emoji" +settingGuide: "Configuración recomendada" +cacheRemoteFiles: "Mantener los archivos remotos en caché" +cacheRemoteFilesDescription: "Si desactivas esta configuración, los archivos remotos se cargarán directamente de los servidores remotos. Desactivar esto reducirá el uso de almacenamiento, pero incrementará el uso de tráfico, ya que no se generarán miniaturas." youCanCleanRemoteFilesCache: "Puedes vaciar la caché pulsando en el botón 🗑️ en el administrador de archivos." -cacheRemoteSensitiveFiles: "Cachear archivos remotos sensibles" -cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles son cargador directamente de la instancia origen sin ser cacheados." +cacheRemoteSensitiveFiles: "Mantener los archivos remotos sensibles en caché" +cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles se cargarán directamente desde los servidores remotos." flagAsBot: "Esta cuenta es un bot" -flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opción. Al hacerlo, esta opción servirá para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustará los sistemas internos de Misskey para que trate a esta cuenta como un bot." -flagAsCat: "Esta cuenta es un gato" -flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opción." -flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografía" -flagShowTimelineRepliesDescription: "Cuando se marca, la línea de tiempo muestra respuestas a otras notas además de las notas del usuario" +flagAsBotDescription: "Activa esta opción si la cuenta es utilizada por un programa. Si se activa, actuará como una etiqueta para otros desarrolladores para prevenir cadenas eternas de interacción con otros bots, y ajustará los sistemas internos de Misskey para tratar esta cuenta de manera acorde." +flagAsCat: "Marcar esta cuenta como gato" +flagAsCatDescription: "Activa esta opción para marcar esta cuenta como un gato." +flagShowTimelineReplies: "Mostrar respuestas en la línea de tiempo" +flagShowTimelineRepliesDescription: "Muestra respuestas de los usuarios a las notas de otros usuarios en la línea de tiempo al activar esta opción." autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de los usuarios que sigues" -addAccount: "Agregar Cuenta" +addAccount: "Agregar cuenta" reloadAccountsList: "Recargar lista de cuentas" loginFailed: "Error al iniciar sesión." -showOnRemote: "Ver en una instancia remota" -continueOnRemote: "Ver en una instancia remota" +showOnRemote: "Ver en instancia remota" +continueOnRemote: "Continuar en una instancia remota" chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub" specifyServerHost: "Especifica una instancia directamente" -inputHostName: "Introduzca el dominio" +inputHostName: "Introduce el dominio" general: "General" wallpaper: "Fondo de pantalla" setWallpaper: "Establecer fondo de pantalla" removeWallpaper: "Quitar fondo de pantalla" searchWith: "Buscar: {q}" -youHaveNoLists: "No tienes listas" -followConfirm: "¿Desea seguir a {name}?" +youHaveNoLists: "No tienes ninguna lista" +followConfirm: "¿Quieres seguir a {name}?" proxyAccount: "Cuenta proxy" -proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad. Así que la cuenta proxy sigue al usuario añadido a la lista" -host: "Host" +proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad, así que la cuenta proxy sigue al usuario añadido a la lista" +host: "Instancia" selectSelf: "Elígete a ti mismo" selectUser: "Elegir usuario" -recipient: "Recipiente" +recipient: "Receptor" annotation: "Anotación" federation: "Federación" -instances: "Instancia" +instances: "Instancias" registeredAt: "Registrado en" -latestRequestReceivedAt: "Ultimo pedido recibido" -latestStatus: "Último status" +latestRequestReceivedAt: "Última petición recibida" +latestStatus: "Último estado" storageUsage: "Almacenamiento usado" -charts: "Chat" +charts: "Métricas" perHour: "por hora" perDay: "por día" stopActivityDelivery: "Dejar de enviar actividades" @@ -220,46 +222,47 @@ silenceThisInstance: "Silenciar esta instancia" mediaSilenceThisInstance: "Silencia la Multimedia(Imágenes,videos...) para este servidor" operations: "Operaciones" software: "Software" +softwareName: "Nombre del software" version: "Versión" metadata: "Metadatos" withNFiles: "{n} archivos" monitor: "Monitor" jobQueue: "Cola de trabajos" -cpuAndMemory: "CPU y Memoria" +cpuAndMemory: "CPU y memoria" network: "Red" disk: "Disco" -instanceInfo: "información de la instancia" +instanceInfo: "Información de la instancia" statistics: "Estadísticas" clearQueue: "Limpiar cola" -clearQueueConfirmTitle: "¿Desea limpiar la cola?" +clearQueueConfirmTitle: "¿Quieres limpiar la cola?" clearQueueConfirmText: "Las notas aún no entregadas no se federarán. Normalmente no se necesita ejecutar esta operación" clearCachedFiles: "Limpiar caché" -clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?" +clearCachedFilesConfirm: "¿Quieres borrar todos los archivos remotos en caché?" blockedInstances: "Instancias bloqueadas" -blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia." +blockedInstancesDescription: "La lista de los dominios de las instancias que quieres bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia." silencedInstances: "Instancias silenciadas" -silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." -mediaSilencedInstances: "Servidores silenciados (Multimedia)" -mediaSilencedInstancesDescription: "Listar las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." +silencedInstancesDescription: "La lista de los dominios de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." +mediaSilencedInstances: "Servidores con multimedia silenciada" +mediaSilencedInstancesDescription: "La lista de los dominios de las instancias cuya multimedia quieres silenciar. Todas las cuentas que pertenezcan a estas instancias serán marcadas como sensibles, y no podrán usar sus emojis personalizados. Esto no afectará a las instancias bloqueadas" federationAllowedHosts: "Servidores federados" -federationAllowedHostsDescription: "Establezca los nombres de los servidores que pueden federarse, separados por una nueva línea." +federationAllowedHostsDescription: "La lista de los dominios de las instancias cuya federación está permitida, separadas por saltos de línea." muteAndBlock: "Silenciar y bloquear" mutedUsers: "Usuarios silenciados" blockedUsers: "Usuarios bloqueados" noUsers: "No hay usuarios" editProfile: "Editar perfil" -noteDeleteConfirm: "¿Desea borrar esta nota?" -pinLimitExceeded: "Ya no se pueden fijar más posts" -intro: "¡La instalación de Misskey ha terminado! Crea el usuario administrador." -done: "Terminado" -processing: "Procesando" +noteDeleteConfirm: "¿Quieres borrar esta nota?" +pinLimitExceeded: "Ya no se pueden fijar más notas" +done: "Hecho" +processing: "Procesando..." +preprocessing: "Preparando" preview: "Vista previa" default: "Predeterminado" defaultValueIs: "Por defecto: {value}" noCustomEmojis: "No hay emojis personalizados" noJobs: "No hay trabajos" federating: "Federando" -blocked: "Bloqueando" +blocked: "Bloqueado" suspended: "Suspendido" all: "Todo" subscribing: "Suscribiendo" @@ -280,8 +283,8 @@ featured: "Destacados" usernameOrUserId: "Nombre o ID del usuario" noSuchUser: "No se encuentra el usuario" lookup: "Búsqueda" -announcements: "Anuncios" -imageUrl: "URL de la imágen" +announcements: "Avisos" +imageUrl: "URL de la imagen." remove: "Borrar" removed: "Borrado" removeAreYouSure: "¿Desea borrar \"{x}\"?" @@ -298,8 +301,10 @@ uploadFromUrl: "Subir desde una URL" uploadFromUrlDescription: "URL del fichero que quieres subir" uploadFromUrlRequested: "Subida solicitada" uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo." +uploadNFiles: "Subir {n} archivos" explore: "Explorar" messageRead: "Ya leído" +readAllChatMessages: "Marcar todos los mensajes como leídos" noMoreHistory: "El historial se ha acabado" startChat: "Nuevo Chat" nUsersRead: "Leído por {n} personas" @@ -314,10 +319,10 @@ remoteUserCaution: "Para el usuario remoto, la información está incompleta" activity: "Actividad" images: "Imágenes" image: "Imágenes" -birthday: "Fecha de nacimiento" +birthday: "Cumpleaños" yearsOld: "{age} años" registeredDate: "Fecha de registro" -location: "Lugar" +location: "Ubicación" theme: "Tema" themeForLightMode: "Tema para usar en Modo Linterna" themeForDarkMode: "Tema para usar en Modo Oscuro" @@ -326,11 +331,13 @@ dark: "Oscuro" lightThemes: "Tema claro" darkThemes: "Tema oscuro" syncDeviceDarkMode: "Sincronice el Modo Oscuro con la configuración de su dispositivo" +switchDarkModeManuallyWhenSyncEnabledConfirm: "{x} está activado ¿Te gustaría desactivar la sincronización y cambiar al modo manual?" drive: "Drive" fileName: "Nombre de archivo" selectFile: "Elegir archivo" selectFiles: "Elegir archivos" selectFolder: "Seleccione una carpeta" +unselectFolder: "Deseleccionar carpeta" selectFolders: "Seleccione carpetas" fileNotSelected: "Archivo no seleccionado." renameFile: "Renombrar archivo" @@ -343,9 +350,10 @@ addFile: "Agregar archivo" showFile: "Examinar archivos" emptyDrive: "El drive está vacío" emptyFolder: "La carpeta está vacía" +dropHereToUpload: "Arrastra los archivos aquí para subirlos." unableToDelete: "No se puede borrar" inputNewFileName: "Ingrese un nuevo nombre de archivo" -inputNewDescription: "Ingrese nueva descripción" +inputNewDescription: "Introducir un nuevo texto alternativo" inputNewFolderName: "Ingrese un nuevo nombre de la carpeta" circularReferenceFolder: "La carpeta de destino es una sub-carpeta de la carpeta que quieres mover." hasChildFilesOrFolders: "No se puede borrar esta carpeta. No está vacía." @@ -424,6 +432,7 @@ antennaExcludeBots: "Excluir bots" antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR" notifyAntenna: "Notificar nueva nota" withFileAntenna: "Sólo notas con archivos adjuntados" +excludeNotesInSensitiveChannel: "Excluir notas en canales sensibles" enableServiceworker: "Activar ServiceWorker" antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva" caseSensitive: "Distinguir mayúsculas de minúsculas" @@ -570,12 +579,14 @@ objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir " s3ForcePathStyleDesc: "Si s3ForcePathStyle esta habilitado el nombre del bucket debe ser especificado como parte de la URL en lugar del nombre de host en la URL. Puede ser necesario activar esta opción cuando se utilice, por ejemplo, Minio en un servidor propio." serverLogs: "Registros del servidor" deleteAll: "Eliminar todos" -showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo" +showFixedPostForm: "Visualizar la ventana de publicación en la parte superior de la línea de tiempo." showFixedPostFormInChannel: "Mostrar el formulario de publicación por encima de la cronología (Canales)" withRepliesByDefaultForNewlyFollowed: "Incluir por defecto respuestas de usuarios recién seguidos en la línea de tiempo" newNoteRecived: "Tienes una nota nueva" +newNote: "Nueva nota" sounds: "Sonidos" sound: "Sonidos" +notificationSoundSettings: "Configuración del sonido de las notificaciones" listen: "Escuchar" none: "Ninguna" showInPage: "Mostrar en la página" @@ -643,7 +654,7 @@ disablePlayer: "Cerrar reproductor" expandTweet: "Expandir tweet" themeEditor: "Editor de temas" description: "Descripción" -describeFile: "Añade una descripción" +describeFile: "Añadir texto alternativo" enterFileDescription: "Introducir un título" author: "Autor" leaveConfirm: "Hay modificaciones sin guardar. ¿Desea descartarlas?" @@ -695,7 +706,7 @@ userSaysSomethingAbout: "{name} dijo algo sobre {word}" makeActive: "Activar" display: "Apariencia" copy: "Copiar" -copiedToClipboard: "Texto copiado al portapapeles" +copiedToClipboard: "Copiado al portapapeles" metrics: "Métricas" overview: "Resumen" logs: "Registros" @@ -704,7 +715,7 @@ database: "Base de datos" channel: "Canal" create: "Crear" notificationSetting: "Ajustes de Notificaciones" -notificationSettingDesc: "Por favor elija el tipo de notificación a mostrar" +notificationSettingDesc: "Por favor elige el tipo de notificación a mostrar" useGlobalSetting: "Usar ajustes globales" useGlobalSettingDesc: "Al activarse, se usará la configuración de notificaciones de la cuenta, al desactivarse se pueden hacer configuraciones particulares." other: "Otro" @@ -736,7 +747,7 @@ system: "Sistema" switchUi: "Cambiar interfaz de usuario" desktop: "Escritorio" clip: "Clip" -createNew: "Crear" +createNew: "Crear Nuevo" optional: "Opcional" createNewClip: "Crear clip nuevo" unclip: "Quitar clip" @@ -767,6 +778,7 @@ lockedAccountInfo: "A menos que configures la visibilidad de tus notas como \"S alwaysMarkSensitive: "Marcar los medios de comunicación como contenido sensible por defecto" loadRawImages: "Cargar las imágenes originales en lugar de mostrar las miniaturas" disableShowingAnimatedImages: "No reproducir imágenes animadas" +disableShowingAnimatedImages_caption: "Si las imágenes animadas no se reproducen independientemente de esta configuración, es posible que la configuración de accesibilidad del navegador o del sistema operativo, los modos de ahorro de energía o funciones similares estén interfiriendo." highlightSensitiveMedia: "Resaltar medios marcados como sensibles" verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación. Por favor, acceda al enlace proporcionado en el correo electrónico para completar la configuración." notSet: "Sin especificar" @@ -783,7 +795,6 @@ thisIsExperimentalFeature: "Se trata de una función experimental. Las especific developer: "Desarrolladores" makeExplorable: "Hacer visible la cuenta en \"Explorar\"" makeExplorableDescription: "Si desactiva esta opción, su cuenta no aparecerá en la sección \"Explorar\"." -showGapBetweenNotesInTimeline: "Mostrar un intervalo entre notas en la línea de tiempo" duplicate: "Duplicar" left: "Izquierda" center: "Centrar" @@ -791,6 +802,7 @@ wide: "Ancho" narrow: "Estrecho" reloadToApplySetting: "Esta configuración sólo se aplicará después de recargar la página. ¿Recargar ahora?" needReloadToApply: "Se requiere un reinicio para la aplicar los cambios" +needToRestartServerToApply: "Se requiere un reinicio para la aplicar los cambios" showTitlebar: "Mostrar la barra de título" clearCache: "Limpiar caché" onlineUsersCount: "{n} usuarios en línea" @@ -832,18 +844,18 @@ jumpToSpecifiedDate: "Saltar a una fecha específica" showingPastTimeline: "Mostrar líneas de tiempo antiguas" clear: "Limpiar" markAllAsRead: "Marcar todo como leído" -goBack: "Deseleccionar" +goBack: "Anterior" unlikeConfirm: "¿Quitar como favorito?" fullView: "Vista completa" quitFullView: "quitar vista completa" addDescription: "Agregar descripción" -userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú de notas individuales" +userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando 'Fijar al perfil' en el menú de notas individuales" notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino" info: "Información" userInfo: "Información del usuario" unknown: "Desconocido" onlineStatus: "En línea" -hideOnlineStatus: "mostrarse como desconectado" +hideOnlineStatus: "Mostrarse como desconectado" hideOnlineStatusDescription: "Ocultar su estado en línea puede reducir la eficacia de algunas funciones, como la búsqueda" online: "En línea" active: "Activo" @@ -872,7 +884,7 @@ popularPosts: "Más vistos" shareWithNote: "Compartir con una nota" ads: "Anuncios" expiration: "Termina el" -startingperiod: "periodo de inicio" +startingperiod: "Comienzo" memo: "Notas" priority: "Prioridad" high: "Alta" @@ -905,8 +917,8 @@ pubSub: "Cuentas Pub/Sub" lastCommunication: "Última comunicación" resolved: "Resuelto" unresolved: "Sin resolver" -breakFollow: "Dejar de seguir" -breakFollowConfirm: "¿Quieres dejar de seguir?" +breakFollow: "Eliminar seguidor" +breakFollowConfirm: "¿De verdad quieres eliminar a este seguidor?" itsOn: "¡Está encendido!" itsOff: "¡Está apagado!" on: "Activado" @@ -957,7 +969,7 @@ threeDays: "Tres días" reflectMayTakeTime: "Puede pasar un tiempo hasta que se reflejen los cambios" failedToFetchAccountInformation: "No se pudo obtener información de la cuenta" rateLimitExceeded: "Se excedió el límite de peticiones" -cropImage: "Recortar imágen" +cropImage: "Recortar Imagen" cropImageAsk: "¿Desea recortar la imagen?" cropYes: "Recortar" cropNo: "Usar como está" @@ -976,8 +988,9 @@ typeToConfirm: "Ingrese {x} para confirmar" deleteAccount: "Borrar cuenta" document: "Documento" numberOfPageCache: "Cantidad de páginas cacheadas" -numberOfPageCacheDescription: "Al aumentar el número mejora la conveniencia pero tambien puede aumentar la carga y la memoria a usarse" +numberOfPageCacheDescription: "Al aumentar el número mejora la conveniencia pero también puede aumentar la carga y la memoria a usarse" logoutConfirm: "¿Cerrar sesión?" +logoutWillClearClientData: "Al cerrar la sesión, la información de configuración del cliente se borra del navegador. Para garantizar que la información de configuración se pueda restaurar al volver a iniciar sesión, active la copia de seguridad automática de la configuración." lastActiveDate: "Utilizado por última vez el" statusbar: "Barra de estado" pleaseSelect: "Selecciona una opción" @@ -996,6 +1009,7 @@ failedToUpload: "La subida falló" cannotUploadBecauseInappropriate: "Este archivo no se puede subir debido a que algunas partes han sido detectadas comoNSFW." cannotUploadBecauseNoFreeSpace: "La subida falló debido a falta de espacio libre en la unidad del usuario." cannotUploadBecauseExceedsFileSizeLimit: "Este archivo supera el peso máximo y no puede ser subido." +cannotUploadBecauseUnallowedFileType: "Incapaz de subir el archivo debido a que es un tipo de archivo no autorizado." beta: "Beta" enableAutoSensitive: "Marcar automáticamente contenido NSFW" enableAutoSensitiveDescription: "Permite la detección y marcado automático de contenido NSFW usando 'Machine Learning' cuando sea posible. Incluso si esta opción está desactivada, puede ser activado para toda la instancia." @@ -1011,10 +1025,13 @@ pushNotificationAlreadySubscribed: "Notificaciones emergentes ya activadas" pushNotificationNotSupported: "El navegador o la instancia no admiten notificaciones push" sendPushNotificationReadMessage: "Eliminar las notificaciones push después de leer las notificaciones y los mensajes" sendPushNotificationReadMessageCaption: "La notificación \"{emptyPushNotificationMessage}\" aparecerá momentáneamente. Esto puede aumentar el consumo de batería del dispositivo." +pleaseAllowPushNotification: "Por favor, permita las notificaciones y la configuración del navegador." +browserPushNotificationDisabled: "No se ha podido obtener permiso para enviar notificaciones." +browserPushNotificationDisabledDescription: "No tienes permiso para enviar notificaciones desde {serverName}. Permite las notificaciones en la configuración de tu navegador y vuelve a intentarlo." windowMaximize: "Maximizar" windowMinimize: "Minimizar" windowRestore: "Regresar" -caption: "Pie de foto" +caption: "Texto alternativo" loggedInAsBot: "Inicio sesión como cuenta bot." tools: "Utilidades" cannotLoad: "No se puede cargar." @@ -1047,6 +1064,7 @@ permissionDeniedError: "Operación denegada" permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción." preset: "Predefinido" selectFromPresets: "Escoger desde predefinidos" +custom: "Personalizado" achievements: "Logros" gotInvalidResponseError: "Respuesta del servidor inválida" gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde" @@ -1073,7 +1091,7 @@ reactionAcceptance: "Aceptación de reacciones" likeOnly: "Sólo 'me gusta'" likeOnlyForRemote: "Sólo reacciones de instancias remotas" nonSensitiveOnly: "Solo no sensible" -nonSensitiveOnlyForLocalLikeOnlyForRemote: "Sólo no contenido sensible (sólo me gusta en remote)" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Sólo no contenido sensible (sólo me gusta en remoto)" rolesAssignedToMe: "Roles asignados a mí" resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?" sensitiveWords: "Palabras sensibles" @@ -1085,6 +1103,7 @@ prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y hiddenTags: "Hashtags ocultos" hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea." notesSearchNotAvailable: "No se puede buscar una nota" +usersSearchNotAvailable: "La búsqueda de usuarios no está disponible." license: "Licencia" unfavoriteConfirm: "¿Desea quitar de favoritos?" myClips: "Mis clips" @@ -1136,11 +1155,11 @@ channelArchiveConfirmTitle: "¿Seguro de archivar {name}?" channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas." thisChannelArchived: "El canal ha sido archivado." displayOfNote: "Mostrar notas" -initialAccountSetting: "Configración inicial de su cuenta\nか\nConfigración de inicio" +initialAccountSetting: "Configración inicial de su cuenta" youFollowing: "Siguiendo" preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generativa)" preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los textos publicados o imágenes en el aprendizaje automático (IA Predictiva / Generativa). Ésto se logra añadiendo una marca respuesta HTML con la cadena \"noai\" al cantenido. Una prevención total no podría lograrse sólo usando ésta marca, ya que puede ser simplemente ignorada." -options: "Opción" +options: "Opciones" specifyUser: "Especificar usuario" lookupConfirm: "¿Quiere informarse?" openTagPageConfirm: "¿Quieres abrir la página de etiquetas?" @@ -1159,6 +1178,7 @@ installed: "Instalado" branding: "Marca" enableServerMachineStats: "Publicar estadísticas de hardware del servidor" enableIdenticonGeneration: "Activar generación de identicon por usuario" +showRoleBadgesOfRemoteUsers: "Mostrar la insignia de rol asignada a los usuarios remotos." turnOffToImprovePerformance: "Desactivar esto puede aumentar el rendimiento." createInviteCode: "Generar invitación" createWithOptions: "Generar con opciones" @@ -1183,8 +1203,8 @@ iHaveReadXCarefullyAndAgree: "He leído el texto {x} y estoy de acuerdo" dialog: "Diálogo" icon: "Avatar" forYou: "Para ti" -currentAnnouncements: "Anuncios actuales" -pastAnnouncements: "Anuncios anteriores" +currentAnnouncements: "Avisos actuales" +pastAnnouncements: "Avisos anteriores" youHaveUnreadAnnouncements: "Hay anuncios sin leer" useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso." replies: "Responder" @@ -1232,12 +1252,11 @@ detachAll: "Quitar todo" angle: "Ángulo" flip: "Echar de un capirotazo" showAvatarDecorations: "Mostrar decoraciones de avatar" -releaseToRefresh: "Soltar para recargar" +releaseToRefresh: "Suelta para recargar" refreshing: "Recargando..." pullDownToRefresh: "Tira hacia abajo para recargar" -disableStreamingTimeline: "Desactivar actualizaciones en tiempo real de la línea de tiempo" useGroupedNotifications: "Mostrar notificaciones agrupadas" -signupPendingError: "Ha habido un problema al verificar tu dirección de correo electrónico. Es posible que el enlace haya caducado." +emailVerificationFailedError: "Se ha producido un error al confirmar tu dirección de correo electrónico. Es posible que el enlace haya caducado." cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción." doReaction: "Añadir reacción" code: "Código" @@ -1250,7 +1269,7 @@ addMfmFunction: "Añadir función MFM" enableQuickAddMfmFunction: "Activar acceso rápido para añadir funciones MFM" bubbleGame: "Bubble Game" sfx: "Efectos de sonido" -soundWillBePlayed: "Se reproducirán efectos sonoros" +soundWillBePlayed: "Con música y efectos sonoros" showReplay: "Ver reproducción" replay: "Reproducir" replaying: "Reproduciendo" @@ -1295,38 +1314,187 @@ passkeyVerificationFailed: "La verificación de la clave de acceso ha fallado." passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificación de la clave de acceso ha sido satisfactoria pero se ha deshabilitado el inicio de sesión sin contraseña." messageToFollower: "Mensaje a seguidores" target: "Para" +testCaptchaWarning: "Esta función está pensada para probar CAPTCHAs.No utilizar en un entorno de producción." prohibitedWordsForNameOfUser: "Palabras prohibidas para nombres de usuario" prohibitedWordsForNameOfUserDescription: "Si alguna de las cadenas de esta lista está incluida en el nombre del usuario, el nombre será denegado. Los usuarios con privilegios de moderador no se ven afectados por esta restricción." yourNameContainsProhibitedWords: "Tu nombre contiene palabras prohibidas" yourNameContainsProhibitedWordsDescription: "Si deseas usar este nombre, por favor contacta con tu administrador/a de tu servidor" +thisContentsAreMarkedAsSigninRequiredByAuthor: " Establecido por el autor: requiere iniciar sesión para ver" lockdown: "Bloqueo" pleaseSelectAccount: "Seleccione una cuenta, por favor." availableRoles: "Roles disponibles " acknowledgeNotesAndEnable: "Activar después de comprender las precauciones" federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador." federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores" +draft: "Borrador" +draftsAndScheduledNotes: "Borradores y notas programadas" +confirmOnReact: "Confirmar la reacción" +reactAreYouSure: "¿Quieres añadir una reacción «{emoji}»?" +markAsSensitiveConfirm: "¿Desea establecer este medio multimedia(Imagen,vídeo...) como sensible?" +unmarkAsSensitiveConfirm: "¿Desea eliminar la designación de sensible para este adjunto?" preferences: "Preferencias" +accessibility: "Accesibilidad" +preferencesProfile: "Configuración del perfil" +copyPreferenceId: "Copiar ID de la configuración" +resetToDefaultValue: "Revertir a valor predeterminado" +overrideByAccount: "Anulado por la cuenta" +untitled: "Sin título" +noName: "No hay nombre." +skip: "Saltar" +restore: "Restaurar" +syncBetweenDevices: "Sincronizar entre dispositivos" +preferenceSyncConflictTitle: "Los valores configurados existen en el servidor." +preferenceSyncConflictText: "Los ajustes de sincronización activados guardarán sus valores en el servidor. Sin embargo, hay valores existentes en el servidor. ¿Qué conjunto de valores desea sobrescribir?" +preferenceSyncConflictChoiceMerge: "Fusionar" +preferenceSyncConflictChoiceServer: "Valores de configuración del servidor" +preferenceSyncConflictChoiceDevice: "Valor configurado en el dispositivo" +preferenceSyncConflictChoiceCancel: "Cancelar la activación de la sincronización" +paste: "Pegar" +emojiPalette: "Paleta emoji" postForm: "Formulario" +textCount: "caracteres" information: "Información" +chat: "Chat" +directMessage: "Chatear" +directMessage_short: "Mensajes" +migrateOldSettings: "Migrar la configuración anterior" +migrateOldSettings_description: "Esto debería hacerse automáticamente, pero si por alguna razón la migración no ha tenido éxito, puede activar usted mismo el proceso de migración manualmente. Se sobrescribirá la información de configuración actual." +compress: "Compresión de la imagen" right: "Derecha" bottom: "Abajo" top: "Arriba" embed: "Insertar" settingsMigrating: "La configuración está siendo migrada, por favor espera un momento... (También puedes migrar manualmente más tarde yendo a Ajustes otros migrar configuración antigua" readonly: "Solo Lectura" +goToDeck: "Volver al Deck" +federationJobs: "Trabajos de Federación" +driveAboutTip: "En Drive, aparecerá una lista de los archivos que has subido en el pasado.
\nPuedes reutilizar estos archivos al adjuntarlos a notas, o puedes subir archivos por adelantado para publicarlos más tarde.
\nTen cuidado al eliminar un archivo, ya que no estará disponible en todos los lugares donde se utilizó (como notas, páginas, avatares, banners, etc.).
\nTambién puedes crear carpetas para organizar tus archivos." +scrollToClose: "Desliza para cerrar" +advice: "Consejos" +realtimeMode: "Modo en tiempo real" +turnItOn: "Activar" +turnItOff: "Desactivar" +emojiMute: "Silenciar emoji" +emojiUnmute: "No silenciar emoji" +muteX: "Silenciar {x}" +unmuteX: "Dejar de silenciar {x}" +abort: "Abortar" +tip: "Consejos y trucos" +redisplayAllTips: "Volver a mostrar todos \"Trucos y consejos\"" +hideAllTips: "Ocultar todos los \"Trucos y consejos\"" +defaultImageCompressionLevel: "Nivel de compresión de la imagen por defecto" +defaultImageCompressionLevel_description: "Baja, conserva la calidad de la imagen pero la medida del archivo es más grande.
Alta, reduce la medida del archivo pero también la calidad de la imagen." +defaultCompressionLevel: "Nivel de compresión predeterminado" +defaultCompressionLevel_description: "Al reducir el ajuste se conserva la calidad, pero aumenta el tamaño del archivo.
Al aumentar el ajuste se reduce el tamaño del archivo, pero disminuye la calidad." +inMinutes: "Minutos" +inDays: "Días" +safeModeEnabled: "El modo seguro está activado" +pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados." +customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado." +themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original." +thankYouForTestingBeta: "¡Gracias por tu colaboración en la prueba de la versión beta!" +createUserSpecifiedNote: "Mencionar al usuario (Nota Directa)" +schedulePost: "Programar una nota" +scheduleToPostOnX: "Programar una nota para {x}" +scheduledToPostOnX: "La nota está programada para el {x}." +schedule: "Programar" +scheduled: "Programado" +widgets: "Widgets" +deviceInfo: "Información del dispositivo" +deviceInfoDescription: "Al realizar consultas técnicas, incluir la siguiente información puede ayudar a resolver el problema." +youAreAdmin: "Eres administrador." +frame: "Marco" +presets: "Predefinido" +zeroPadding: "Relleno cero" +nothingToConfigure: "No hay nada que configurar" +_imageEditing: + _vars: + caption: "Título del archivo" + filename: "Nombre de archivo" + filename_without_ext: "Nombre del archivo sin la extensión" + year: "Año de rodaje" + month: "Mes de la fotografía" + day: "Día de la fotografía" + hour: "Hora" + minute: "Minuto" + second: "Segundo" + camera_model: "Nombre de la cámara" + camera_lens_model: "Modelo de lente" + camera_mm: "Distancia focal" + camera_mm_35: "Distancia Focal (Equivalente a formato de 35mm)" + camera_f: "Apertura de diafragma" + camera_s: "Velocidad de Obturación" + camera_iso: "Sensibilidad ISO" + gps_lat: "Latitud" + gps_long: "Longitud" +_imageFrameEditor: + title: "Edición de Fotos" + tip: "Decora tus imágenes con marcos y etiquetas que contengan metadatos." + header: "Título" + footer: "Pie de página" + borderThickness: "Ancho del borde" + labelThickness: "Ancho de la etiqueta" + labelScale: "Escala de la Etiqueta" + centered: "Alinear al centro" + captionMain: "Pie de foto (Grande)" + captionSub: "Pie de foto (Pequeño)" + availableVariables: "Variables disponibles" + withQrCode: "Código QR" + backgroundColor: "Color de fondo" + textColor: "Color del texto" + font: "Fuente" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "¿Descartar cambios no guardados?" + failedToLoadImage: "Error al cargar la imagen" +_compression: + _quality: + high: "Calidad alta" + medium: "Calidad media" + low: "Calidad baja" + _size: + large: "Tamaño grande" + medium: "Tamaño mediano" + small: "Tamaño pequeño" +_order: + newest: "Más reciente primero" + oldest: "Más antiguos primero" _chat: + messages: "Mensajes" noMessagesYet: "Aún no hay mensajes" newMessage: "Mensajes nuevos" individualChat: "Chat individual" individualChat_description: "Mantén una conversación privada con otra persona." + roomChat: "Sala de Chat" + roomChat_description: "Una sala de chat que puede tener varias personas.\nTambién puedes invitar a personas que no permiten chats privados si aceptan la invitación." + createRoom: "Crear sala" + inviteUserToChat: "Invitar usuarios para empezar a chatear" + yourRooms: "Salas creadas" + joiningRooms: "Salas que te has unido" invitations: "Invitar" + noInvitations: "No hay invitación." + history: "Historial" noHistory: "No hay datos en el historial" + noRooms: "No te has unido a ninguna sala " + inviteUser: "Invitar usuarios" + sentInvitations: "Invitaciones enviadas" + join: "Unirse" + ignore: "Ignorar" + leave: "Dejar sala" members: "Miembros" + searchMessages: "Buscar mensajes" home: "Inicio" send: "Enviar" + newline: "Nueva línea" + muteThisRoom: "Silenciar esta sala" + deleteRoom: "Borrar sala" + chatNotAvailableForThisAccountOrServer: "El chat no está habilitado en este servidor ni para esta cuenta." + chatIsReadOnlyForThisAccountOrServer: "El chat es de sólo lectura en esta instancia o esta cuenta. No puedes escribir nuevos mensajes ni crear/unirte a salas de chat." chatNotAvailableInOtherAccount: "La función de chat está desactivada para el otro usuario." cannotChatWithTheUser: "No se puede iniciar un chat con este usuario" cannotChatWithTheUser_description: "El chat no está disponible o la otra parte no ha habilitado el chat." + youAreNotAMemberOfThisRoomButInvited: "No eres participante en esta sala, pero has recibido una invitación. Por favor, acepta la invitación para unirte." + doYouAcceptInvitation: "¿Aceptas la invitación?" chatWithThisUser: "Chatear" thisUserAllowsChatOnlyFromFollowers: "Este usuario sólo acepta chats de seguidores." thisUserAllowsChatOnlyFromFollowing: "Este usuario sólo acepta chats de los usuarios a los que sigue." @@ -1342,19 +1510,100 @@ _chat: none: "Nadie" _emojiPalette: palettes: "Paleta\n" + enableSyncBetweenDevicesForPalettes: "Activar la sincronización de paletas entre dispositivos" + paletteForMain: "Paleta principal" + paletteForReaction: "Paleta utilizada para las reacciones" _settings: + driveBanner: "Puedes gestionar y configurar la unidad, comprobar su uso y configurar los ajustes de carga de archivos." + pluginBanner: "Puedes ampliar las funciones del cliente con plugins. Puedes instalar plugins, configurarlos y gestionarlos individualmente." + notificationsBanner: "Puede configurar los tipos y el alcance de las notificaciones del servidor y las notificaciones push." api: "API" webhook: "Webhook" + serviceConnection: "Integraciones" + serviceConnectionBanner: "Gestione y configure tokens de acceso y Webhooks para integrarse con aplicaciones o servicios externos." + accountData: "Datos de la cuenta" + accountDataBanner: "Exportación e importación para gestionar los datos de la cuenta." + muteAndBlockBanner: "Puedes configurar y gestionar ajustes para ocultar contenidos y restringir acciones a usuarios específicos." + accessibilityBanner: "Puedes personalizar el aspecto y el comportamiento del cliente y configurar los ajustes para optimizar su uso." + privacyBanner: "Puedes configurar opciones relacionadas con la privacidad de la cuenta, como la visibilidad del contenido, la posibilidad de descubrir la cuenta y la aprobación de seguimiento." + securityBanner: "Puedes configurar opciones relacionadas con la seguridad de la cuenta, como la contraseña, los métodos de inicio de sesión, las aplicaciones de autenticación y Passkeys." + preferencesBanner: "Puedes configurar el comportamiento general del cliente según tus preferencias." + appearanceBanner: "Puedes configurar el aspecto y la visualización del cliente según tus preferencias." + soundsBanner: "Puedes configurar los ajustes de sonido para la reproducción en el cliente." + timelineAndNote: "Líneas del tiempo y notas" + makeEveryTextElementsSelectable: "Hacer que todos los elementos de texto sean seleccionables" + makeEveryTextElementsSelectable_description: "Activar esta opción puede reducir la usabilidad en algunas situaciones." + useStickyIcons: "Hacer que los iconos te sigan cuando desplaces" + enableHighQualityImagePlaceholders: "Mostrar marcadores de posición para imágenes de alta calidad" + uiAnimations: "Animaciones de la interfaz de usuario" + showNavbarSubButtons: "Mostrar los sub-botones en la barra de navegación." + ifOn: "Si está activado" + ifOff: "Si está desactivado" + enableSyncThemesBetweenDevices: "Sincronizar los temas instalados entre dispositivos." + enablePullToRefresh: "Tirar para actualizar" + enablePullToRefresh_description: "Si utilizas un ratón, arrastra mientras pulsas la rueda de desplazamiento." + realtimeMode_description: "Establece una conexión con el servidor y actualiza el contenido en tiempo real. Esto puede aumentar el tráfico y el consumo de memoria." + contentsUpdateFrequency: "Frecuencia de adquisición del contenido." + contentsUpdateFrequency_description: "Cuanto mayor sea el valor, más se actualiza el contenido, pero disminuye el rendimiento y aumenta el tráfico y el consumo de memoria." + contentsUpdateFrequency_description2: "Cuando el modo en tiempo real está activado, el contenido se actualiza en tiempo real independientemente de esta configuración." + showUrlPreview: "Mostrar la vista previa de la URL" + showAvailableReactionsFirstInNote: "Mostrar las reacciones disponibles en la parte superior." + showPageTabBarBottom: "Mostrar la barra de pestañas de la página en la parte inferior." + emojiPaletteBanner: "Puedes registrar ajustes preestablecidos como paletas para que se muestren permanentemente en el selector de emojis, o personalizar el método de visualización del selector." + enableAnimatedImages: "Habilitar imágenes animadas" + settingsPersistence_title: "Persistencia de la configuración" + settingsPersistence_description1: "Habilitar la persistencia de la configuración evita que se pierda la información de configuración." + settingsPersistence_description2: "Es posible que no se pueda habilitar esta función dependiendo del entorno." + _chat: + showSenderName: "Mostrar el nombre del remitente" + sendOnEnter: "Intro para enviar" +_preferencesProfile: + profileName: "Nombre de perfil" + profileNameDescription: "Establece un nombre que identifique al dispositivo" + profileNameDescription2: "Por ejemplo: \"PC Principal\",\"Teléfono\"" + manageProfiles: "Administrar perfiles" + shareSameProfileBetweenDevicesIsNotRecommended: "No recomendamos compartir el mismo perfil en varios dispositivos." + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "Si hay ajustes que deseas sincronizar en varios dispositivos, activa la opción «Sincronizar en varios dispositivos» individualmente para cada uno de ellos." +_preferencesBackup: + autoBackup: "Respaldo automático" + restoreFromBackup: "Restaurar desde copia de seguridad" + noBackupsFoundTitle: "No se encontró una copia de seguridad" + noBackupsFoundDescription: "No se han encontrado copias de seguridad creadas automáticamente, pero si has guardado manualmente un archivo de copia de seguridad, puedes importarlo y restaurarlo." + selectBackupToRestore: "Selecciona una copia de seguridad para restaurar" + youNeedToNameYourProfileToEnableAutoBackup: "Se debe establecer un nombre de perfil para activar la copia de seguridad automática." + autoPreferencesBackupIsNotEnabledForThisDevice: "La copia de seguridad automática de los ajustes no está activada en este dispositivo." + backupFound: "Copia de seguridad de los ajustes encontrada " + forceBackup: "Forzar una copia de seguridad de la configuración" _accountSettings: requireSigninToViewContents: "Se requiere iniciar sesión para ver el contenido" requireSigninToViewContentsDescription1: "Requiere iniciar sesión para ver todas las notas y otros contenidos que hayas creado. Se espera que esto evite que los rastreadores recopilen información." + requireSigninToViewContentsDescription2: "El contenido no se mostrará en vistas previas de URL (OGP), incrustado en páginas web o en servidores que no admitan citas de notas." + requireSigninToViewContentsDescription3: "Estas restricciones pueden no aplicarse a los contenidos federados de otros servidores remotos." + makeNotesFollowersOnlyBefore: "Hacer que las notas antiguas sólo se muestren a los seguidores" + makeNotesFollowersOnlyBeforeDescription: "Mientras esta función esté activada, sólo los seguidores podrán ver las notas que hayan superado la fecha y hora establecidas o que hayan estado visibles durante un tiempo determinado. Cuando se desactive, también se restablecerá el estado de publicación de la nota." + makeNotesHiddenBefore: "Hacer privadas las notas antiguas " + makeNotesHiddenBeforeDescription: "Mientras esta función esté activada, las notas que hayan pasado la fecha y hora fijadas o hayan transcurrido el tiempo establecido sólo serán visibles para ti (se harán privadas). Si la desactivas, también se restablecerá el estado público de las notas." + mayNotEffectForFederatedNotes: "Notas federadas por un servidor remoto pueden no verse afectadas." + mayNotEffectSomeSituations: "Estas restricciones son simplificadas. Pueden no aplicarse en algunas situaciones, como cuando se visualiza en un servidor remoto o durante la moderación." + notesHavePassedSpecifiedPeriod: "Notas publicadas durante el siguiente tiempo específico" + notesOlderThanSpecifiedDateAndTime: "Notas antes de la fecha y hora especificadas" _abuseUserReport: + forward: "Reenviar" + forwardDescription: "Reenvía el informe a un servidor/instancia remoto como cuenta anónima del sistema." + resolve: "Resuelto" accept: "Acepte" reject: "repudio" + resolveTutorial: "Si el contenido del informe es legítimo, selecciona \"Aceptar\" para marcarlo como resuelto.\nSi el contenido del informe es ilegítimo, selecciona \"Rechazar\" para ignorarlo." _delivery: + status: "Estado de la entrega" stop: "Suspendido" + resume: "Resumen de entrega" _type: none: "Publicando" + manuallySuspended: "Suspendido manualmente" + goneSuspended: "El servidor se ha suspendido debido a la eliminación del servidor" + autoSuspendedForNotResponding: "El servidor se suspende debido a que el servidor no responde." + softwareSuspended: "Suspendido porque este software ya no se distribuye a" _bubbleGame: howToPlay: "Cómo jugar" hold: "Mantener" @@ -1362,7 +1611,7 @@ _bubbleGame: score: "Puntos" scoreYen: "Cantidad de dinero ganada" highScore: "Puntuación más alta" - maxChain: "Número máximo de cadenas" + maxChain: "Número máximo de combos" yen: "{yen} Yenes" estimatedQty: "{qty} Piezas" scoreSweets: "{onigiriQtyWithUnit} Onigiris" @@ -1379,7 +1628,7 @@ _announcement: tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos." readConfirmTitle: "¿Marcar como leído?" readConfirmText: "Esto marcará el contenido de \"{title}\" como leído." - shouldNotBeUsedToPresentPermanentInfo: "Dado que puede impactar en la experiencia de usuario de forma significativa, es recomendable usar notificaciones en el flujo de información en vez de información persistente." + shouldNotBeUsedToPresentPermanentInfo: "Se recomienda utilizar los avisos para publicar información que requiera inmediatez, en lugar de hacerlo constantemente, ya que esto perjudica especialmente la UX de los nuevos usuarios." dialogAnnouncementUxWarn: "Mostrar dos o más notificaciones en formato diálogo a la vez puede impactar en la experiencia de usuario de forma significativa, úsalos con cuidado." silence: "Silenciar notificaciones" silenceDescription: "Si lo activas, no enviarás notificación sobre este anuncio y el usuario no tendrá que leerlo." @@ -1425,9 +1674,9 @@ _initialTutorial: title: "El concepto de Línea de tiempo" description1: "Misskey proporciona múltiples líneas de tiempo basadas en su uso (algunas pueden no estar disponibles dependiendo de las políticas de la instancia)." home: "Puedes ver los posts de las cuentas que sigues." - local: "Puedes ver los posts de todos los usuarios de este servidor." + local: "Puedes ver los posts de todos los usuarios de este servidor (también llamado instancia)." social: "Se ven los posts de la línea de tiempo de inicio junto con los de la línea de tiempo local." - global: "Puedes ver notas de todos los servidores conectados." + global: "Puedes ver notas de todos los servidores (instancias) conectados." description2: "Puedes cambiar la línea de tiempo en la parte superior de la pantalla cuando quieras." description3: "Además, hay listas de líneas de tiempo y listas de canales. Para más detalle, por favor visita este enlace: {link}" _postNote: @@ -1437,14 +1686,14 @@ _initialTutorial: description: "Puedes limitar quién puede ver tu nota." public: "Tu nota será visible para todos los usuarios." home: "Publicar solo en la línea de tiempo de Inicio. La nota se verá en tu perfil, la verán tus seguidores y también cuando sea renotada." - followers: "Visible solo para seguidores. Sólo tus seguidores podrán ver la nota, y no podrá ser renotada por otras personas." + followers: "Visible solo para seguidores. Solo tus seguidores podrán ver la nota, y no podrá ser renotada por otras personas." direct: "Visible sólo para usuarios específicos, y el destinatario será notificado. Puede usarse como alternativa a la mensajería directa." doNotSendConfidencialOnDirect1: "¡Ten cuidado cuando vayas a enviar información sensible!" - doNotSendConfidencialOnDirect2: "Los administradores del servidor pueden leer lo que escribes. Ten cuidado cuando envíes información sensible en notas directas en servidores no confiables." + doNotSendConfidencialOnDirect2: "Los administradores del servidor, también llamado instancia, pueden leer lo que escribes. Ten cuidado cuando envíes información sensible en notas directas en servidores o instancias no confiables." localOnly: "Publicando con esta opción seleccionada, la nota no se federará hacia otros servidores. Los usuarios de otros servidores no podrán ver estas notas directamente, sin importar los ajustes seleccionados más arriba." _cw: title: "Alerta de contenido (CW)" - description: "En lugar de mostrarse el contenido de la nota, se mostrará lo que escribas en el campo \"comentarios\". Pulsando en \"leer más\" desplegará el contenido de la nota." + description: "En lugar de mostrarse el contenido de la nota, se mostrará lo que escribas en el campo \"comentarios\". Pulsando en \"Ver más\" desplegará el contenido de la nota." _exampleNote: cw: "¡Esto te hará tener hambre!" note: "Acabo de comerme un donut de chocolate glaseado 🍩😋" @@ -1480,6 +1729,38 @@ _serverSettings: fanoutTimelineDescription: "Incrementa el rendimiento de forma significativa cuando se obtienen las líneas de tiempo y reduce la carga en la base de datos. A cambio, el uso de la memoria en Redis incrementará. Considera desactivar esta opción en caso de que tu servidor tenga poca memoria o detectes inestabilidad." fanoutTimelineDbFallback: "Cargar desde la base de datos" fanoutTimelineDbFallbackDescription: "Cuando esta opción está habilitada, la carga de peticiones adicionales de la línea de tiempo se hará desde la base de datos cuando éstas no se encuentren en la caché. Al deshabilitar esta opción se reduce la carga del servidor, pero limita el número de líneas de tiempo que pueden obtenerse." + reactionsBufferingDescription: "Cuando se activa, el rendimiento durante la creación de reacciones mejorará considerablemente, reduciendo la carga de la base de datos. Sin embargo, aumentará el uso de memoria de Redis." + remoteNotesCleaning: "Limpieza automática de notas (publicaciones) remotas" + remoteNotesCleaning_description: "Al habilitar esta opción, se limpiarán periódicamente las entradas remotas antiguas que no se consultan, lo que evitará que la base de datos se sature." + remoteNotesCleaningMaxProcessingDuration: "Tiempo máximo de funcionamiento continuo del proceso de limpieza" + remoteNotesCleaningExpiryDaysForEachNotes: "Días mínimos para conservar las notas" + inquiryUrl: "URL de consulta " + inquiryUrlDescription: "Especifica una URL para el formulario de consulta al responsable del servidor o una página web para la información de contacto." + openRegistration: "Registros Abiertos" + openRegistrationWarning: "Abrir registros conlleva riesgos. Se recomienda solo habilitarlos si tienes un sistema en el cual puedes monitorear continuamente el servidor y respondes inmediatamente en caso de que haya cualquier problema." + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Si no se ha detectado por un tiempo actividad de un moderador, este ajuste será automáticamente desactivado para prevenir el spam. " + deliverSuspendedSoftware: "Software suspendido." + deliverSuspendedSoftwareDescription: "Puede especificar un rango de nombres y versiones del software del servidor para detener la entrega, por ejemplo, debido a vulnerabilidades. Esta información sobre la versión la proporciona el servidor y su fiabilidad no está garantizada. Se puede utilizar una especificación de rango para especificar una versión, pero se recomienda especificar una versión previa, como >= 2024.3.1-0, ya que especificar >= 2024.3.1 no incluirá versiones personalizadas como 2024.3.1-custom.0." + singleUserMode: "Modo de usuario único" + singleUserMode_description: "Si eres el único usuario de este servidor, activar este modo optimizará su rendimiento." + signToActivityPubGet: "Firmar solicitudes GET de Activitypub." + signToActivityPubGet_description: "Normalmente, debería estar activada. Deshabilitarlo puede mejorar los problemas relacionados con la federación, pero por otro lado podría deshabilitar la federación hacia otros servidores." + proxyRemoteFiles: "Proxy de archivos remotos" + proxyRemoteFiles_description: "Cuando se activa, el servidor proxy sirve archivos remotos. Esto es útil para generar miniaturas de imágenes y proteger la privacidad del usuario." + allowExternalApRedirect: "Permitir redirecciones para consultas vía ActivityPub" + allowExternalApRedirect_description: "Si se activa, otros servidores pueden consultar contenidos de terceros a través de este servidor, pero esto puede dar lugar a la suplantación de contenidos." + userGeneratedContentsVisibilityForVisitor: "Visibilidad de contenido generado por un usuario a invitados" + userGeneratedContentsVisibilityForVisitor_description: "Esto es útil para evitar problemas causados por contenidos remotos inapropiados que no estén bien moderados y que se publiquen involuntariamente en Internet a través de su propio servidor." + userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor." + restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?" + restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán" + entrancePageStyle: "Estilo de la página de inicio" + showTimelineForVisitor: "Mostrar la línea de tiempo" + showActivitiesForVisitor: "Mostrar actividades" + _userGeneratedContentsVisibilityForVisitor: + all: "Todo es público." + localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado" + none: "Todo es privado" _accountMigration: moveFrom: "Trasladar de otra cuenta a ésta" moveFromSub: "Crear un alias para otra cuenta." @@ -1764,7 +2045,7 @@ _role: isConditionalRole: "Esto es un rol condicional" isPublic: "Publicar rol" descriptionOfIsPublic: "Cualquiera puede ver los usuarios asignados a este rol. También, el perfil del usuario mostrará este rol." - options: "Opción" + options: "Opciones" policies: "Política" baseRole: "Rol base" useBaseValue: "Usar los valores del rol base" @@ -1776,6 +2057,8 @@ _role: descriptionOfIsExplorable: "La línea de tiempo de éste rol y la lista de usuarios serán públicos si se activa.." displayOrder: "Posición" descriptionOfDisplayOrder: "Entre más alto el número, mayor es la posición en la interfaz." + preserveAssignmentOnMoveAccount: "Preservar los roles asignados durante la migración" + preserveAssignmentOnMoveAccount_description: "Si está activada, este rol se transferirá a la cuenta de destino cuando se migre una cuenta con este rol." canEditMembersByModerator: "Permitir a los moderadores editar los miembros" descriptionOfCanEditMembersByModerator: "Si se activa, los moderadores, al igual que los administradores, serán capaces de asignar/quitar usuarios a éste rol. Si se desactiva, sólo los administradores podrán hacerlo." priority: "Prioridad" @@ -1795,7 +2078,10 @@ _role: canManageCustomEmojis: "Administrar emojis personalizados" canManageAvatarDecorations: "Administrar decoraciones de avatar" driveCapacity: "Capacidad del drive" + maxFileSize: "Tamaño máximo de archivo que se puede cargar." + maxFileSize_caption: "Los proxies inversos o las CDN pueden tener diferentes valores de configuración aguas arriba." alwaysMarkNsfw: "Siempre marcar archivos como NSFW" + canUpdateBioMedia: "Puede editar un icono o una imagen de fondo (banner)" pinMax: "Máximo de notas fijadas" antennaMax: "Máximo de antenas" wordMuteMax: "Máximo de caracteres en palabras silenciadas" @@ -1808,8 +2094,21 @@ _role: descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos" canHideAds: "Puede ocultar anuncios" canSearchNotes: "Uso de la búsqueda de notas" + canSearchUsers: "Uso de la búsqueda de usuarios" canUseTranslator: "Uso de traductor" avatarDecorationLimit: "Número máximo de decoraciones de avatar" + canImportAntennas: "Permitir la importación de antenas" + canImportBlocking: "Permitir la importación de bloqueos" + canImportFollowing: "Permitir la importación de seguidos" + canImportMuting: "Permitir la importación de silenciados" + canImportUserLists: "Permitir la importación de listas" + chatAvailability: "Permitir Chats" + uploadableFileTypes: "Tipos de archivos que se pueden cargar." + uploadableFileTypes_caption: "Especifica los tipos MIME/archivos permitidos. Se pueden especificar varios tipos MIME separándolos con una nueva línea, y se pueden especificar comodines con un asterisco (*). (por ejemplo, image/*)" + uploadableFileTypes_caption2: "Es posible que no se detecten algunos tipos de archivos. Para permitir estos archivos, añade {x} a la especificación." + noteDraftLimit: "Número de posibles borradores de notas del servidor" + scheduledNoteLimit: "Máximo número de notas programadas que se pueden crear simultáneamente." + watermarkAvailable: "Disponibilidad de la función de marca de agua" _condition: roleAssignedTo: "Asignado a roles manuales" isLocal: "Usuario local" @@ -1818,6 +2117,7 @@ _role: isBot: "Usuarios Bot" isSuspended: "Usuario suspendido" isLocked: "Cuentas privadas" + isExplorable: "Hacer que la cuenta sea visible en las búsquedas" createdLessThan: "Menos de X han pasado desde la creación de la cuenta" createdMoreThan: "Más de X han pasado desde la creación de la cuenta" followersLessThanOrEq: "Tiene X o menos seguidores" @@ -1856,11 +2156,11 @@ _accountDelete: accountDelete: "Eliminar Cuenta" mayTakeTime: "La eliminación de la cuenta es un proceso que precisa de carga. Puede pasar un tiempo hasta que se complete si es mucho el contenido creado y los archivos subidos." sendEmail: "Cuando se termine de borrar la cuenta, se enviará un correo a la dirección usada para el registro." - requestAccountDelete: "Pedir la eliminación de la cuenta." + requestAccountDelete: "Solicitar la eliminación de la cuenta." started: "El proceso de eliminación ha comenzado." inProgress: "La eliminación está en proceso." _ad: - back: "Deseleccionar" + back: "Anterior" reduceFrequencyOfThisAd: "Mostrar menos este anuncio." hide: "No mostrar" timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor." @@ -1911,7 +2211,7 @@ _registry: key: "Clave" keys: "Clave" domain: "Dominio" - createKey: "Crear una llave" + createKey: "Crear una clave" _aboutMisskey: about: "Misskey es un software de código abierto, desarrollado por syuilo desde el 2014" contributors: "Principales colaboradores" @@ -1927,7 +2227,7 @@ _aboutMisskey: _displayOfSensitiveMedia: respect: "Esconder medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles" - force: "Esconder todala multimedia" + force: "Esconder toda la multimedia" _instanceTicker: none: "No mostrar" remote: "Mostrar a usuarios remotos" @@ -1968,10 +2268,12 @@ _theme: install: "Instalar tema" manage: "Gestor de temas" code: "Código del tema" + copyThemeCode: "Copiar el código del tema" description: "Descripción" installed: "{name} ha sido instalado" installedThemes: "Temas instalados" builtinThemes: "Temas integrados" + instanceTheme: "Tema del servidor (o también denominado: tema de la instancia)" alreadyInstalled: "Este tema ya está instalado" invalid: "El formato del tema no es válido" make: "Crear tema" @@ -2001,7 +2303,7 @@ _theme: indicator: "Indicador" panel: "Panel" shadow: "Sombra" - header: "Cabezal" + header: "Título" navBg: "Fondo de la barra lateral" navFg: "Texto de la barra lateral" navActive: "Texto de la barra lateral (activo)" @@ -2025,7 +2327,6 @@ _theme: buttonBg: "Fondo de botón" buttonHoverBg: "Fondo de botón (hover)" inputBorder: "Borde de los campos de entrada" - driveFolderBg: "Fondo de capeta del drive" badge: "Medalla" messageBg: "Fondo de chat" fgHighlighted: "Texto resaltado" @@ -2034,6 +2335,7 @@ _sfx: noteMy: "Nota (a mí mismo)" notification: "Notificaciones" reaction: "Al seleccionar una reacción" + chatMessage: "Mensajes del Chat" _soundSettings: driveFile: "Usar un archivo de audio en Drive" driveFileWarn: "Selecciona un archivo de audio en Drive." @@ -2041,6 +2343,7 @@ _soundSettings: driveFileTypeWarnDescription: "Selecciona un archivo de audio" driveFileDurationWarn: "La duración del audio es demasiado larga." driveFileDurationWarnDescription: "Usar un audio de larga duración puede llegar a molestar mientras usas Misskey. ¿Quieres continuar?" + driveFileError: "No puedo cargar el sonido. Por favor cambia la configuración." _ago: future: "Futuro" justNow: "Justo ahora" @@ -2065,6 +2368,7 @@ _time: minute: "Minutos" hour: "Horas" day: "Días" + month: "Mes(es)" _2fa: alreadyRegistered: "Ya has completado la configuración." registerTOTP: "Registrar aplicación autenticadora" @@ -2076,7 +2380,7 @@ _2fa: setupCompleted: "Configuración completada" step4: "Ahora cuando inicie sesión, ingrese el mismo token" securityKeyNotSupported: "Tu navegador no soporta claves de autenticación." - registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad." + registerTOTPBeforeKey: "Por favor. configura una aplicación de autenticación para registrar una llave de seguridad." securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN" registerSecurityKey: "Registrar una llave de seguridad" securityKeyName: "Ingresa un nombre para la clave" @@ -2180,6 +2484,7 @@ _permissions: "read:federation": "Ver instancias federadas" "write:report-abuse": "Crear reportes de usuario" "write:chat": "Administrar chat" + "read:chat": "Explorar Chats" _auth: shareAccessTitle: "Permisos de la aplicación" shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?" @@ -2188,8 +2493,12 @@ _auth: permissionAsk: "Esta aplicación requiere los siguientes permisos" pleaseGoBack: "Por favor, vuelve a la aplicación" callback: "Volviendo a la aplicación" + accepted: "Acceso concedido." denied: "Acceso denegado" + scopeUser: "Operar como el siguiente usuario" pleaseLogin: "Se requiere un inicio de sesión para darle permisos a la aplicación" + byClickingYouWillBeRedirectedToThisUrl: "Cuando el acceso es concedido, serás automáticamente redireccionado a la siguiente URL" + alreadyAuthorized: "Esta aplicación ya ha obtenido acceso." _antennaSources: all: "Todas las notas" homeTimeline: "Notas de los usuarios que sigues" @@ -2220,7 +2529,7 @@ _widgets: digitalClock: "Reloj digital" unixClock: "Reloj UNIX" federation: "Federación" - instanceCloud: "Nube de palabras de la instancia" + instanceCloud: "Nube de Instancias Federadas" postForm: "Formulario" slideshow: "Diapositivas" button: "Botón" @@ -2235,6 +2544,45 @@ _widgets: chooseList: "Seleccione una lista" clicker: "Cliqueador" birthdayFollowings: "Hoy cumplen años" + chat: "Chatear" +_widgetOptions: + showHeader: "Mostrar encabezados" + transparent: "Hacer fondo transparente" + height: "Altura" + _button: + colored: "Color" + _clock: + size: "Tamaño" + thickness: "Grosor de la aguja" + thicknessThin: "Delgada" + thicknessMedium: "Normal" + thicknessThick: "Gruesa" + graduations: "Marcas del dial" + graduationDots: "Puntos" + graduationArabic: "Números decimales" + fadeGraduations: "Desvanecer la escala" + sAnimation: "Animación de la manecilla de los segundos" + sAnimationElastic: "Real" + sAnimationEaseOut: "Suave" + twentyFour: "Formato 24 horas" + labelTime: "Hora" + labelTz: "Zona horaria" + labelTimeAndTz: "Hora y zona horaria" + timezone: "Zona horaria" + showMs: "Mostrar milisegundos" + showLabel: "Mostrar etiqueta" + _jobQueue: + sound: "Reproducir sonido" + _rss: + url: "URL del canal RSS" + refreshIntervalSec: "Intervalo de actualización (En segundos)" + maxEntries: "Número máximo de elementos a mostrar" + _rssTicker: + shuffle: "Orden de visualización aleatorio" + duration: "Velocidad de desplazamiento del baner (En segundos)" + reverse: "Desplázate en la dirección opuesta." + _birthdayFollowings: + period: "Duración" _cw: hide: "Ocultar" show: "Ver más" @@ -2244,7 +2592,7 @@ _poll: noOnlyOneChoice: "Se necesitan al menos 2 opciones" choiceN: "Opción {n}" noMore: "No se pueden agregar más" - canMultipleVote: "Permitir más de una respuesta" + canMultipleVote: "Permitir seleccionar varias opciones" expiration: "Termina el" infinite: "Sin límite de tiempo" at: "Elegir fecha y hora" @@ -2274,9 +2622,25 @@ _visibility: disableFederation: "No federado" disableFederationDescription: "No enviar a otras instancias" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Hay archivos que no se han cargado, ¿deseas descartarlos y cerrar el formulario?" + uploaderTip: "El archivo aún no se ha cargado. Desde el menú de archivos, puedes cambiar el nombre, recortar la imagen, añadir una marca de agua y configurar la compresión, entre otras opciones. Los archivos se suben automáticamente al publicar una nota." replyPlaceholder: "Responder a esta nota" quotePlaceholder: "Citar esta nota" channelPlaceholder: "Publicar en el canal" + showHowToUse: "Mostrar el tutorial de este formulario" + _howToUse: + content_title: "Cuerpo" + content_description: "Introduce aquí el contenido que deseas publicar." + toolbar_title: "Barras de herramientas" + toolbar_description: "Puedes adjuntar archivos o realizar encuestas, añadir anotaciones o hashtags e insertar emojis o menciones." + account_title: "Menú de la cuenta" + account_description: "Puedes cambiar entre cuentas para publicar o ver una lista de borradores y publicaciones programadas guardadas en tu cuenta." + visibility_title: "Visibilidad" + visibility_description: "Puedes configurar la visibilidad de tus notas." + menu_title: "Menú" + menu_description: "Puedes realizar otras acciones, como guardar borradores, programar publicaciones y configurar reacciones." + submit_title: "Botón de publicar" + submit_description: "Publica tus notas pulsando este botón. También puedes publicar utilizando Ctrl + Intro / Cmd + Intro." _placeholders: a: "¿Qué haces?" b: "¿Te pasó algo?" @@ -2288,16 +2652,19 @@ _profile: name: "Nombre" username: "Nombre de usuario" description: "Descripción" - youCanIncludeHashtags: "Puedes añadir hashtags" + youCanIncludeHashtags: "También puedes incluir hashtags en tu biografía" metadata: "información adicional" metadataEdit: "Editar información adicional" - metadataDescription: "Muestra la información adicional en el perfil" + metadataDescription: "Usando esto puedes mostrar campos de información adicionales en tu perfil." metadataLabel: "Etiqueta" metadataContent: "Contenido" changeAvatar: "Cambiar avatar" changeBanner: "Cambiar banner" verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo." avatarDecorationMax: "Puedes añadir un máximo de {max} decoraciones de avatar." + followedMessage: "Mensaje cuando te han seguido" + followedMessageDescription: "Puedes establecer un mensaje de bienvenida para nuevos seguidores." + followedMessageDescriptionForLockedAccount: "Si apruebas manualmente seguidores, el mensaje se mostrará al seguidor en el momento de la aprobación." _exportOrImport: allNotes: "Todas las notas" favoritedNotes: "Notas favoritas" @@ -2308,7 +2675,7 @@ _exportOrImport: userLists: "Listas" excludeMutingUsers: "Excluir usuarios silenciados" excludeInactiveUsers: "Excluir usuarios inactivos" - withReplies: "Incluir respuestas de los usuarios importados en la línea de tiempo" + withReplies: "Si el archivo no incluye información sobre si las respuestas deben incluirse en la línea de tiempo, las respuestas realizadas por el importador deben incluirse en la línea de tiempo." _charts: federation: "Federación" apRequest: "Pedidos" @@ -2387,6 +2754,7 @@ _pages: eyeCatchingImageSet: "Elegir imagen llamativa" eyeCatchingImageRemove: "Borrar imagen llamativa" chooseBlock: "Agregar bloque" + enterSectionTitle: "Escribe el título de la sección" selectType: "Elegir tipo" contentBlocks: "Contenido" inputBlocks: "Entrada" @@ -2418,9 +2786,12 @@ _notification: youReceivedFollowRequest: "Has mandado una solicitud de seguimiento" yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada" pollEnded: "Estan disponibles los resultados de la encuesta" + scheduledNotePosted: "Una nota programada ha sido publicada" + scheduledNotePostFailed: "Ha fallado la publicación de una nota programada" newNote: "Nueva nota" unreadAntennaNote: "Antena {name}" roleAssigned: "Rol asignado" + chatRoomInvitationReceived: "Invitado a la sala de chat." emptyPushNotificationMessage: "Se han actualizado las notificaciones push" achievementEarned: "Logro desbloqueado" testNotification: "Notificación de prueba" @@ -2428,23 +2799,34 @@ _notification: sendTestNotification: "Enviar notificación de prueba" notificationWillBeDisplayedLikeThis: "Las notificaciones tendrán este aspecto" reactedBySomeUsers: "{n} usuarios han reaccionado" + likedBySomeUsers: "{n} usuarios les gustó tu nota" renotedBySomeUsers: "{n} usuarios han renotado" followedBySomeUsers: "Seguido por {n} usuarios" + flushNotification: "Limpiar notificaciones" + exportOfXCompleted: "La exportación de {x} ha sido completada." + login: "Alguien ha iniciado sesión" + createToken: "Token de acceso creado" + createTokenDescription: "Si no tienes ni idea, elimina el token de acceso a través de \"{text}\"." _types: all: "Todo" note: "Nuevas notas" follow: "Siguiendo" mention: "Menciones" reply: "Respuestas" - renote: "Renotar" + renote: "Renotas" quote: "Citar" reaction: "Reacción" pollEnded: "La encuesta terminó" + scheduledNotePosted: "Publicación programada con éxito" + scheduledNotePostFailed: "Publicación programada fallida" receiveFollowRequest: "Recibió una solicitud de seguimiento" followRequestAccepted: "El seguimiento fue aceptado" roleAssigned: "Rol asignado" + chatRoomInvitationReceived: "Invitado a la sala de chat." achievementEarned: "Logro desbloqueado" + exportCompleted: "La exportación se ha completado" login: "Iniciar sesión" + createToken: "Crear tokens de acceso" test: "Pruebas de nofiticaciones" app: "Notificaciones desde aplicaciones" _actions: @@ -2454,7 +2836,11 @@ _notification: _deck: alwaysShowMainColumn: "Siempre mostrar la columna principal" columnAlign: "Alinear columnas" + columnGap: "Margen entre columnas" + deckMenuPosition: "Posición del menú Deck" + navbarPosition: "Posición de la barra de navegación" addColumn: "Agregar columna" + newNoteNotificationSettings: "Configuración de las notificaciones para notas nuevas" configureColumn: "Ajustes de columna" swapLeft: "Mover a la izquierda" swapRight: "Mover a la derecha" @@ -2471,6 +2857,15 @@ _deck: useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple" usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada" flexible: "Autoajustar ancho" + enableSyncBetweenDevicesForProfiles: "Activar la sincronización de la información de perfiles entre dispositivos." + showHowToUse: "Ver la descripción de la interfaz de usuario" + _howToUse: + addColumn_title: "Añadir columna" + addColumn_description: "Puede seleccionar y añadir tipos de columnas." + settings_title: "Configuración de la interfaz de usuario" + settings_description: "Puedes configurar la interfaz de usuario en detalle." + switchProfile_title: "Cambiar de perfil" + switchProfile_description: "Puedes guardar diseños de interfaz de usuario como perfiles y cambiar entre ellos en cualquier momento." _columns: main: "Principal" widgets: "Widgets" @@ -2482,6 +2877,7 @@ _deck: mentions: "Menciones" direct: "Notas directas" roleTimeline: "Linea de tiempo del rol" + chat: "Chatear" _dialog: charactersExceeded: "¡Has excedido el límite de caracteres! Actualmente {current} de {max}." charactersBelow: "¡Estás por debajo del límite de caracteres! Actualmente {current} de {min}." @@ -2493,8 +2889,10 @@ _drivecleaner: orderByCreatedAtAsc: "Fecha ascendente" _webhookSettings: createWebhook: "Crear Webhook" + modifyWebhook: "Editar webhook" name: "Nombre" secret: "Secreto" + trigger: "Disparador" active: "Activado" _events: follow: "Cuando se sigue a alguien" @@ -2505,14 +2903,31 @@ _webhookSettings: reaction: "Cuando se recibe una reacción" mention: "Cuando hay una mención" _systemEvents: + abuseReport: "Cuando se recibe un nuevo informe de moderación" + abuseReportResolved: "Cuando se resuelve un informe de moderación" userCreated: "Cuando se crea el usuario." + inactiveModeratorsWarning: "Cuando un moderador ha estado inactivo por un tiempo" + inactiveModeratorsInvitationOnlyChanged: "Cuando un moderador ha estado inactivo durante un tiempo, y el servidor se cambia a sólo por invitación" + deleteConfirm: "¿Estás seguro de querer eliminar el Webhook?" + testRemarks: "Haz clic en el botón de la derecha del switch para mandar una prueba Webhook con datos ficticios" _abuseReport: _notificationRecipient: + createRecipient: "Añadir destinatario a los informes" + modifyRecipient: "Editar un destinatario en el informe de moderación\n" + recipientType: "Tipo de notificación" _recipientType: mail: "Correo" webhook: "Webhook" + _captions: + mail: "Enviar un correo electrónico a todos los moderadores cuando reciban un informe de moderación" + webhook: "Enviar una notificación al SystemWebhook cuando se reciba o se resuelva un informe de moderación" keywords: "Palabras Clave" + notifiedUser: "Usuarios a notificar" + notifiedWebhook: "Webhook a utilizar" + deleteConfirm: "¿Estás seguro de que deseas borrar el destinatario del informe de moderación?" _moderationLogTypes: + clearQueue: "Borrar la cola de trabajos" + promoteQueue: "Reintentar el trabajo en la cola" createRole: "Rol creado" deleteRole: "Rol eliminado" updateRole: "Rol actualizado" @@ -2536,9 +2951,12 @@ _moderationLogTypes: resetPassword: "Resetear contraseña" suspendRemoteInstance: "Instancia remota suspendida" unsuspendRemoteInstance: "Suspensión de instancia remota retirada" + updateRemoteInstanceNote: "Nota de moderación de una instancia remota actualizada" markSensitiveDriveFile: "Archivo marcado como sensible" unmarkSensitiveDriveFile: "Archivo marcado como no sensible" resolveAbuseReport: "Reporte resuelto" + forwardAbuseReport: "Informe reenviado" + updateAbuseReportNote: "Nota de moderación de un informe actualizada" createInvitation: "Generar invitación" createAd: "Anuncio creado" deleteAd: "Anuncio eliminado" @@ -2548,6 +2966,18 @@ _moderationLogTypes: deleteAvatarDecoration: "Decoración de avatar eliminada" unsetUserAvatar: "Quitar decoración de avatar de este usuario" unsetUserBanner: "Quitar banner de este usuario" + createSystemWebhook: "Crear un SystemWebhook" + updateSystemWebhook: "Actualizar SystemWebhook " + deleteSystemWebhook: "Borrar SystemWebHook" + createAbuseReportNotificationRecipient: "Crear un destinatario para el informe de moderación" + updateAbuseReportNotificationRecipient: "Destinatario de los informes actualizados" + deleteAbuseReportNotificationRecipient: "Destinatario de los informes borrado" + deleteAccount: "Cuenta Borrada" + deletePage: "Página borrada" + deleteFlash: "Juego borrado" + deleteGalleryPost: "Publicación de la galería, eliminada" + deleteChatRoom: "Borrar sala del chat" + updateProxyAccountDescription: "Actualizar la descripción de la cuenta proxy" _fileViewer: title: "Detalles del archivo" type: "Tipo de archivo" @@ -2555,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "Subido el" attachedNotes: "Notas adjuntas" + usage: "Utilizado" thisPageCanBeSeenFromTheAuthor: "Esta página solo puede ser vista por el autor." _externalResourceInstaller: title: "Instalar desde sitio externo" @@ -2602,35 +3033,410 @@ _dataSaver: _avatar: title: "Avatares animados" description: "Desactiva la animación de los avatares. Las imágenes animadas pueden llegar a ser de mayor tamaño que las normales, por lo que al desactivarlas puedes reducir el consumo de datos." - _urlPreview: - title: "Vista previa de URLs" - description: "Desactiva la carga de vistas previas de las URLs." + _urlPreviewThumbnail: + title: "Ocultar las miniaturas de las vistas previas de URL" + description: "Las imágenes en miniatura de la vista previa de URL no se pueden cargar " + _disableUrlPreview: + title: "Desactivar la vista previa de las URL" + description: "Desactiva la función de previsualización de la URL. A diferencia de solo las imágenes en miniatura, esta función reduce la carga de la propia información vinculada." _code: title: "Resaltar código" description: "Si se usa resaltado de código en MFM, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos." _hemisphere: N: "Hemisferio norte" S: "Hemisferio sur" + caption: "Usado en algunos clientes para determinar la estación del año" _reversi: reversi: "Reversi" + gameSettings: "Configuración del juego" + chooseBoard: "Elegir tablero" + blackOrWhite: "Negras/Blancas" + blackIs: "{name} juega con negras" rules: "Reglas" + thisGameIsStartedSoon: "El juego comenzará en breve" + waitingForOther: "Esperando el turno del adversario" + waitingForMe: "Esperando tu turno" + waitingBoth: "Prepárate" + ready: "Listo" + cancelReady: "No estoy listo" + opponentTurn: "Turno del oponente" + myTurn: "¡Tu turno!" + turnOf: "Le toca a {name}" + pastTurnOf: "Turno de {name}" + surrender: "Rendirse" + surrendered: "Te has rendido" + timeout: "Se acabó el tiempo" + drawn: "Empate" won: "{name} ha ganado" + black: "Negras" + white: "Blancas" total: "Total" + turnCount: "Turno {count}" + myGames: "Mis rondas" + allGames: "Todos los juegos" + ended: "Finalizado" + playing: "Jugando actualmente" + isLlotheo: "El que tenga menos fichas gana (LLoTheO)" + loopedMap: "Mapa en bucle" + canPutEverywhere: "Las fichas se pueden poner a cualquier lugar\n" + timeLimitForEachTurn: "Tiempo límite por jugada." + freeMatch: "Partida libre" + lookingForPlayer: "Buscando oponente" + gameCanceled: "La partida ha sido cancelada." + shareToTlTheGameWhenStart: "Compartir la partida en la línea de tiempo cuando comience " + iStartedAGame: "¡La partida ha comenzado!" + opponentHasSettingsChanged: "El oponente ha cambiado su configuración" + allowIrregularRules: "Reglas irregulares (completamente libre)" + disallowIrregularRules: "Sin reglas irregulares " + showBoardLabels: "Mostrar el número de línea y la letra de columna en el tablero de juego." + useAvatarAsStone: "Usar los avatares de los usuarios como fichas\n" +_offlineScreen: + title: "Fuera de línea. No se puede conectar con el servidor" + header: "Incapaz de conectar con el servidor" _urlPreviewSetting: + title: "Configuración para la previsualización de la URL" + enable: "Activar la vista previa de URL" + allowRedirect: "Permitir la redirección de la visualización previa" + allowRedirectDescription: "Si una URL tiene una redirección establecida, puede activar esta función para seguir la redirección y mostrar una vista previa del contenido redirigido. Si se desactiva, se ahorrarán recursos del servidor, pero no se mostrará el contenido redirigido." timeout: "Timeout de la carga de vista previa de las URLs (ms)" + timeoutDescription: "Si se tarda más de este valor en obtener la vista previa, ésta no se generará." maximumContentLength: "Content-Length Máximo (bytes)" + maximumContentLengthDescription: "Si Content-Length es superior a este valor, no se generará la vista previa." + requireContentLength: "Genere la vista previa sólo si puede obtener Content-Length" + requireContentLengthDescription: "Si el otro servidor no devuelve Content-Length, no se generará la vista previa." userAgent: "User-Agent" + userAgentDescription: "Establece el User-Agent que se utilizará al recuperar vistas previas. Si se deja en blanco, se utilizará el User-Agent por defecto." + summaryProxy: "Proxy endpoints para generar vistas previas" + summaryProxyDescription: "La vista previa se genera usando Summaly proxy, no la genera el mismo Misskey." + summaryProxyDescription2: "Los siguientes parámetros se vinculan al proxy como cadena de consulta (query string). Si el proxy no los admite, los valores se ignoran." _mediaControls: pip: "Picture in Picture" playbackRate: "Velocidad de reproducción" loop: "Reproducción en bucle" +_contextMenu: + title: "Menú contextual" + app: "Aplicación" + appWithShift: "Aplicación con la tecla shift" + native: "Interfaz nativa (del navegador web)" +_gridComponent: + _error: + requiredValue: "Este valor es obligatorio" + columnTypeNotSupport: "La validación con expresión regular sólo se admite para columnas de tipo:texto." + patternNotMatch: "Este valor no coincide con el patrón en {pattern}" + notUnique: "Este valor debe ser único" +_roleSelectDialog: + notSelected: "No seleccionado" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copiar filas seleccionadas" + copySelectionRanges: "Copiar selección" + deleteSelectionRows: "Borrar las líneas seleccionadas" + deleteSelectionRanges: "Borrar las filas de la selección" + searchSettings: "Ajustes de búsqueda" + searchSettingCaption: "Establecer criterios de búsqueda detallados." + searchLimit: "Límite de resultados" + sortOrder: "Ordenar" + registrationLogs: "Log de registros " + registrationLogsCaption: "Los registros se mostrarán al actualizar o borrar Emojis. Desaparecerán después de actualizarlos o eliminarlos, pasar a una nueva página o recargar." + alertEmojisRegisterFailedDescription: "No se ha podido actualizar o borrar el emoji. Por favor comprueba el log del registro para más detalles." + _logs: + showSuccessLogSwitch: "Mostrar registro de éxito" + failureLogNothing: "No hay log de fallos" + logNothing: "No hay logs" + _remote: + selectionRowDetail: "Detalle de la línea seleccionada" + importSelectionRows: "Importar las líneas seleccionadas" + importSelectionRangesRows: "Importar las filas seleccionadas" + importEmojisButton: "Importar los Emojis marcados" + confirmImportEmojisTitle: "Importar Emojis" + confirmImportEmojisDescription: "Importar {count} Emoji(s) recibidos del servidor remoto. Por favor, presta mucha atención a la licencia del Emoji. ¿Estás seguro de continuar?" + _local: + tabTitleList: "Lista de emojis registrados" + tabTitleRegister: "Registro de Emojis" + _list: + emojisNothing: "No hay Emojis registrados" + markAsDeleteTargetRows: "Marcar las filas seleccionadas como objetivo a eliminar" + markAsDeleteTargetRanges: "Selección de filas para su eliminación" + alertUpdateEmojisNothingDescription: "No hay Emojis actualizados" + alertDeleteEmojisNothingDescription: "No hay Emojis para borrar" + confirmMovePage: "¿Quieres cambiar de página?" + confirmChangeView: "¿De verdad quieres cambiar la vista?" + confirmUpdateEmojisDescription: "Actualizar {count} Emoji(s). ¿Deseas continuar?" + confirmDeleteEmojisDescription: "Borrar {count} Emoji(s) seleccionados. ¿Deseas continuar?" + confirmResetDescription: "Se restablecerán todos los cambios hechos hasta ahora." + confirmMovePageDesciption: "Se han realizado cambios en los Emojis de esta página.\nSi abandonas la página sin guardar, se descartarán todos los cambios realizados en esta página." + dialogSelectRoleTitle: "Buscar Emojis por rol" + _register: + uploadSettingTitle: "Ajustes de carga" + uploadSettingDescription: "En esta pantalla, puedes configurar el comportamiento al cargar Emojis." + directoryToCategoryLabel: "Introduce el nombre del directorio en el campo \"categoría\"" + directoryToCategoryCaption: "Cuando arrastres y sueltes un directorio, introduce el nombre del directorio en el campo \"categoría\"." + confirmRegisterEmojisDescription: "Registra los Emojis de la lista como nuevos Emojis personalizados. ¿Estás seguro de continuar? (Para evitar sobrecargas, sólo {count} Emoji(s) en una sola operación)" + confirmClearEmojisDescription: "Descartar las ediciones y borrar los Emojis de la lista. ¿Estás seguro de continuar?" + confirmUploadEmojisDescription: "Cargar los {count} archivo(s) arrastrado(s) y soltado(s) en la unidad. ¿Estás seguro de continuar?" +_embedCodeGen: + title: "Personalizar el código de incrustación" + header: "Mostrar encabezados" + autoload: "Cargar más automáticamente (no recomendado)" + maxHeight: "Altura máxima" + maxHeightDescription: "0 desactiva el ajuste del valor máximo. Para evitar que el widget siga creciendo verticalmente, especifica algún valor." + maxHeightWarn: "El límite de altura máxima está desactivado (0). Si esto no estaba previsto, establece la altura máxima en algún valor." + previewIsNotActual: "La visualización difiere de la incrustación real porque excede el rango mostrado en la pantalla de vista previa." + rounded: "Bordes Redondeados" + border: "Añadir un borde al marco exterior" + applyToPreview: "Aplicar a la vista previa" + generateCode: "Crear el código para incrustar" + codeGenerated: "El código ha sido generado" + codeGeneratedDescription: "Pegue el código generado en su sitio web para incrustar el contenido." +_selfXssPrevention: + warning: "Advertencia" + title: "\"Pegar algo en esta pantalla\" es un timo." + description1: "Si pegas algo aquí, un usuario malintencionado podría secuestrar tu cuenta o robar tu información personal." + description2: "Si no entiendes que estás pegando exactamente, %cdetente ahora mismo y cierra esta ventana" + description3: "Para más información visita esto {link}" _followRequest: - recieved: "Petición de seguimiento recibida" - sent: "Petición de seguimiento enviada" + recieved: "Solicitud de seguimiento recibida" + sent: "Solicitud de seguimiento enviada" _remoteLookupErrors: + _federationNotAllowed: + title: "Incapaz de comunicarse con este servidor." + description: "Es posible que se haya desactivado la comunicación con este servidor o que haya sido bloqueado.\nPonte en contacto con el administrador del servidor.." + _uriInvalid: + title: "La URI es inválida" + description: "Ha habido un problema con la dirección introducida. Comprueba que no hayas escrito caracteres que no pueden ser usados en la URI" + _requestFailed: + title: "Solicitud fallida." + description: "Ha fallado la comunicación con este servidor. Es posible que el servidor no funcione. Asegúrese también de que no ha introducido un URI no válido o inexistente." + _responseInvalid: + title: "La respuesta no es válida" + description: "Has podido comunicarte con este servidor, pero los datos obtenidos eran incorrectos. Si estás consultando contenidos remotos a través de un servidor de terceros, vuelve a realizar la consulta utilizando un URI que pueda obtenerse del servidor de origen." _noSuchObject: title: "No se encuentra" + description: "No se ha encontrado el recurso solicitado, por favor, vuelve a comprobar el URI." +_captcha: + verify: "Por favor verifica el CAPTCHA" + testSiteKeyMessage: "Puedes comprobar la vista previa introduciendo los valores de prueba para el sitio y las claves secretas.\nPara más detalles, consulta la página siguiente.\n" + _error: + _requestFailed: + title: "Ha fallado la solicitud del CAPTCHA" + text: "Por favor, ejecútalo después de un rato o comprueba los ajustes de nuevo." + _verificationFailed: + title: "Ha fallado la validación del CAPTCHA" + text: "Comprueba que los ajustes son los correctos." + _unknown: + title: "Error en el CAPTCHA." + text: "Se ha producido un error inesperado." +_bootErrors: + title: "Fallo al cargar" + serverError: "Si el problema persiste después de esperar un momento y volver a cargar, póngase en contacto con el administrador del servidor con el siguiente ID de error." + solution: "Lo siguiente puede resolver el problema." + solution1: "Actualiza tu navegador web y el sistema operativo a la última versión" + solution2: "Desactiva el AdBlocker" + solution3: "Borra la memoria caché del navegador web " + solution4: "(Navegador Tor) configura dom.webaudio.enabled a true" + otherOption: "Otras opciones" + otherOption1: "Borra la configuración y la memoria caché del cliente" + otherOption2: "Iniciar el cliente simple" + otherOption3: "Iniciar la herramienta de reparación" + otherOption4: "Iniciar Misskey en modo seguro" _search: searchScopeAll: "Todo" searchScopeLocal: "Local" + searchScopeServer: "Especifica el servidor (Instancia)" searchScopeUser: "Especificar usuario" + pleaseEnterServerHost: "Introduce la dirección del servidor/Instancia" + pleaseSelectUser: "Selecciona un usuario, por favor" + serverHostPlaceholder: "Ejemplo: misskey.example.com" +_serverSetupWizard: + installCompleted: "¡La instalación de Misskey se ha completado!" + firstCreateAccount: "Para comenzar, crea una cuenta de administrador" + accountCreated: "¡La cuenta de administrador se ha creado! " + serverSetting: "Configuración del servidor" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Este asistente te facilita una configuración óptima del servidor." + settingsYouMakeHereCanBeChangedLater: "Los ajustes que han sido cambiados a través de este asistente pueden ser modificados más tarde." + howWillYouUseMisskey: "¿Cómo vas a usar Misskey?" + _use: + single: "Servidor para un único usuario." + single_description: "Utilízalo como tu propio servidor dedicado." + single_youCanCreateMultipleAccounts: "Se pueden crear múltiples cuentas según sea necesario, incluso cuando se opera como servidor unipersonal." + group: "Servidor de grupo" + group_description: "Invita otros usuarios de confianza y úsalo con más de una persona.\n" + open: "Servidor público" + open_description: "Permite a cualquiera registrarse" + openServerAdvice: "Aceptar un número no determinado de usuarios comporta algunos riesgos. Se recomienda operar con un sistema de moderación fiable para hacer frente a los problemas." + openServerAntiSpamAdvice: "Para evitar que su servidor se convierta en un trampolín para el spam, también debe prestar mucha atención a la seguridad habilitando funciones anti-bot como reCAPTCHA." + howManyUsersDoYouExpect: "¿Cuántas personas esperas?" + _scale: + small: "Menos de 100 (escala pequeña)" + medium: "Más de 100 y menos de 1000 (escala media)\n" + large: "Más de 1000(escala grande)" + largeScaleServerAdvice: "Los grandes servidores pueden requerir conocimientos avanzados de infraestructura, como equilibrio de carga y replicación de bases de datos." + doYouConnectToFediverse: "¿Quieres conectarte al Fediverso?" + doYouConnectToFediverse_description1: "Cuando se conecta a una red de servidores distribuidos (Fediverso), el contenido puede intercambiarse con otros servidores." + doYouConnectToFediverse_description2: "Conectarse con el Fediverso también se conoce como \"federación\"." + youCanConfigureMoreFederationSettingsLater: "Los ajustes avanzados, como la especificación de servidores federados, pueden configurarse más adelante." + remoteContentsCleaning: "Limpieza automática de los contenidos recibidos" + remoteContentsCleaning_description: "La federación puede dar lugar a un flujo continuo de contenido. Al habilitar la limpieza automática, se eliminará del servidor el contenido obsoleto y sin referencias para ahorrar espacio de almacenamiento." + adminInfo: "Información del administrador" + adminInfo_description: "Establece la información del administrador para recibir consultas." + adminInfo_mustBeFilled: "Esta información debe ser introducida en el caso de registros abiertos o la federación esté activada." + followingSettingsAreRecommended: "Se recomienda los siguientes ajustes" + applyTheseSettings: "Aplicar estos ajustes" + skipSettings: "Omitir configuración" + settingsCompleted: "¡Configuración inicial del servidor completada!" + settingsCompleted_description: "Gracias por tu tiempo. Ahora que está todo listo puedes empezar a utilizar el servidor inmediatamente." + settingsCompleted_description2: "La configuración avanzada del servidor pueden realizarse a través del \"Panel de control\"." + donationRequest: "Por favor Dona" + _donationRequest: + text1: "Misskey es un software libre desarrollado por voluntarios." + text2: "Agradeceríamos su apoyo para que podamos seguir desarrollando este software en el futuro." + text3: "También hay beneficios especiales para los donantes" +_uploader: + editImage: "Editar la imagen" + compressedToX: "Comprimir a {x}" + savedXPercent: "Guardando {x}%" + abortConfirm: "Algunos archivos no se han cargado, ¿deseas cancelar?" + doneConfirm: "Algunos archivos no se han cargado, ¿deseas continuar de todos modos?" + maxFileSizeIsX: "El tamaño máximo de archivo que se puede cargar es de {x}" + allowedTypes: "Tipos de archivos que se pueden cargar." + tip: "El archivo aún no se ha cargado, por lo que este cuadro de diálogo te permite confirmar, renombrar, comprimir y recortar el archivo antes de cargarlo. Cuando esté listo, puedes iniciar la carga pulsando el botón \"Cargar\"." +_clientPerformanceIssueTip: + title: "Si crees que el consumo de batería es demasiado alto" + makeSureDisabledAdBlocker: "Por favor, desactiva el bloqueador de publicidad." + makeSureDisabledAdBlocker_description: "Los bloqueadores de anuncios pueden afectar al rendimiento. Asegúrate de que no están activados en tu sistema o en las funciones/extensiones de tu navegador." + makeSureDisabledCustomCss: "Desactiva el CSS personalizado" + makeSureDisabledCustomCss_description: "Anular estilos puede afectar al rendimiento. Asegúrate de que el CSS personalizado o las extensiones que sobrescriben estilos no están activados." + makeSureDisabledAddons: "Desactiva las extensiones " + makeSureDisabledAddons_description: "Algunas extensiones pueden interferir con el comportamiento del cliente y afectar al rendimiento. Por favor, deshabilita las extensiones de tu navegador y comprueba si esto mejora la situación." +_clip: + tip: "Clip es una función que permite organizar varias notas." +_userLists: + tip: "Las listas pueden contener cualquier usuario que especifiques al crearlas, la lista creada puede mostrarse entonces como una línea de tiempo mostrando solo los usuarios especificados." +watermark: "Marca de Agua" +defaultPreset: "Por defecto" +_watermarkEditor: + tip: "Se puede añadir a la imagen una marca de agua, como información crediticia." + quitWithoutSaveConfirm: "¿Descartar cambios no guardados?" + driveFileTypeWarn: "Este archivo es incompatible" + driveFileTypeWarnDescription: "Elegir una imagen" + title: "Editar la marca de agua" + cover: "Cubrir todo" + repeat: "Repetir" + preserveBoundingRect: "Ajuste para evitar que se desborde al rotar." + opacity: "Opacidad" + scale: "Tamaño" + text: "Texto" + qr: "Código QR" + position: "Posición" + margin: "Margen" + type: "Tipo" + image: "Imágenes" + advanced: "Avanzado" + angle: "Ángulo" + stripe: "Rayas" + stripeWidth: "Anchura de línea" + stripeFrequency: "Número de líneas." + polkadot: "Patrón de Lunares" + checker: "Patrón de Damas / Tablero de Ajedrez" + polkadotMainDotOpacity: "Opacidad del círculo principal" + polkadotMainDotRadius: "Tamaño del círculo principal." + polkadotSubDotOpacity: "Opacidad del círculo secundario" + polkadotSubDotRadius: "Tamaño del círculo secundario." + polkadotSubDotDivisions: "Número de subpuntos." + leaveBlankToAccountUrl: "Si dejas este campo en blanco, se utilizará la URL de tu cuenta." + failedToLoadImage: "Error al cargar la imagen" +_imageEffector: + title: "Efecto" + addEffect: "Añadir Efecto" + discardChangesConfirm: "¿Ignorar cambios y salir?" + failedToLoadImage: "Error al cargar la imagen" + _fxs: + chromaticAberration: "Aberración Cromática" + glitch: "Glitch" + mirror: "Espejo" + invert: "Invertir colores" + grayscale: "Blanco y negro" + blur: "Difuminar" + pixelate: "Pixelar" + colorAdjust: "Corrección de Color" + colorClamp: "Ajuste de Tono" + colorClampAdvanced: "Ajuste de Tono avanzado" + distort: "Distorsión" + threshold: "Binarización" + zoomLines: "Líneas de Impacto" + stripe: "Rayas" + polkadot: "Patrón de Lunares" + checker: "Patrón de Damas / Tablero de Ajedrez" + blockNoise: "Ruido de Bloque" + tearing: "Rasgado de Imagen (Tearing)" + fill: "Relleno de color" + _fxProps: + angle: "Ángulo" + scale: "Tamaño" + size: "Tamaño" + radius: "Radio" + samples: "Tamaño de muestra" + offset: "Posición" + color: "Color" + opacity: "Opacidad" + normalize: "Normalización" + amount: "Cantidad" + lightness: "Brillo" + contrast: "Contraste" + hue: "Tonalidad" + brightness: "Luminancia" + saturation: "Saturación" + max: "Valor máximo" + min: "Valor mínimo" + direction: "Dirección" + phase: "Fase" + frequency: "Frecuencia" + strength: "Intensidad" + glitchChannelShift: "Desfase" + seed: "Valor de la semilla" + redComponent: "Canal Rojo" + greenComponent: "Canal Verde" + blueComponent: "Canal Azul" + threshold: "Umbral" + centerX: "Centrar X" + centerY: "Centrar Y" + zoomLinesSmoothing: "Suavizado" + zoomLinesSmoothingDescription: "El suavizado y el ancho de línea de zoom no se pueden utilizar juntos." + zoomLinesThreshold: "Ancho de línea del zoom" + zoomLinesMaskSize: "Diámetro del centro" + zoomLinesBlack: "Cambiar color de las líneas de impacto a negro." + circle: "Círculo" +drafts: "Borrador" +_drafts: + select: "Seleccionar borradores" + cannotCreateDraftAnymore: "Se ha superado el número de borradores que se pueden crear." + cannotCreateDraft: "No se pueden crear borradores con este contenido." + delete: "Eliminar borrador" + deleteAreYouSure: "¿Quieres borrar el borrador?" + noDrafts: "No hay borradores disponibles." + replyTo: "Responder a {user}" + quoteOf: "Citar las notas de {user}" + postTo: "Destino a {channel}" + saveToDraft: "Guardar como borrador" + restoreFromDraft: "Restaurar desde los borradores" + restore: "Restaurar" + listDrafts: "Listar los borradores" + schedule: "Programar Nota" + listScheduledNotes: "Lista de notas programadas" + cancelSchedule: "Cancelar programación" +qr: "Código QR" +_qr: + showTabTitle: "Apariencia" + readTabTitle: "Escanear" + shareTitle: "{name} {acct}" + shareText: "¡Sígueme en el Fediverso!" + chooseCamera: "Seleccione cámara" + cannotToggleFlash: "No se puede activar el flash" + turnOnFlash: "Encender el flash" + turnOffFlash: "Apagar el flash" + startQr: "Reiniciar el lector de códigos QR" + stopQr: "Detener el lector de códigos QR" + noQrCodeFound: "No se encontró el código QR" + scanFile: "Escanear imagen desde un dispositivo" + raw: "Texto" + mfm: "MFM" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 3a6f520ae6..63b8f3bb55 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -5,11 +5,12 @@ introMisskey: "Bienvenue ! Misskey est un service de microblogage décentralis poweredByMisskeyDescription: "{name} est l'un des services propulsés par la plateforme ouverte Misskey (appelée \"instance Misskey\")." monthAndDay: "{day}/{month}" search: "Rechercher" +reset: "Réinitialiser" notifications: "Notifications" username: "Nom d’utilisateur·rice" password: "Mot de passe" initialPasswordForSetup: "Mot de passe initial pour la configuration" -initialPasswordIsIncorrect: "Mot de passe initial pour la configuration est incorrecte" +initialPasswordIsIncorrect: "Le mot de passe initial pour la configuration est incorrect" initialPasswordForSetupDescription: "Utilisez le mot de passe que vous avez entré pour le fichier de configuration si vous avez installé Misskey vous-même.\nSi vous utilisez un service d'hébergement Misskey, utilisez le mot de passe fourni.\nSi vous n'avez pas défini de mot de passe, laissez le champ vide pour continuer." forgotPassword: "Mot de passe oublié" fetchingAsApObject: "Récupération depuis le fédiverse …" @@ -48,6 +49,7 @@ pin: "Épingler sur le profil" unpin: "Désépingler" copyContent: "Copier le contenu" copyLink: "Copier le lien" +copyRemoteLink: "Copier le lien de la note" copyLinkRenote: "Copier le lien de la renote" delete: "Supprimer" deleteAndEdit: "Supprimer et réécrire" @@ -62,8 +64,8 @@ copyNoteId: "Copier l'identifiant de la note" copyFileId: "Copier l'identifiant du fichier" copyFolderId: "Copier l'identifiant du dossier" copyProfileUrl: "Copier l'URL du profil" -searchUser: "Chercher un·e utilisateur·rice" -searchThisUsersNotes: "Cherchez les notes de cet·te utilisateur·rice" +searchUser: "Chercher un utilisateur" +searchThisUsersNotes: "Cherchez les notes de cet utilisateur" reply: "Répondre" loadMore: "Afficher plus …" showMore: "Voir plus" @@ -81,6 +83,8 @@ files: "Fichiers" download: "Télécharger" driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier « {name} » ? Les notes avec ce fichier joint seront aussi supprimées." unfollowConfirm: "Désirez-vous vous désabonner de {name} ?" +cancelFollowRequestConfirm: "Est-te vous sur de vouloir annuler la demande de suivi de {name} ?" +rejectFollowRequestConfirm: "Refuser la demande de suivi de {name} ?" exportRequested: "Vous avez demandé une exportation. L’opération pourrait prendre un peu de temps. Une fois terminée, le fichier sera ajouté au Drive." importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." lists: "Listes" @@ -118,6 +122,8 @@ cantReRenote: "Impossible de renoter une Renote." quote: "Citer" inChannelRenote: "Renoter dans le canal" inChannelQuote: "Citer dans le canal" +renoteToChannel: "Renoter sur le canal" +renoteToOtherChannel: "Renoter sur un autre canal" pinnedNote: "Note épinglée" pinned: "Épingler sur le profil" you: "Vous" @@ -212,6 +218,7 @@ blockThisInstance: "Bloquer cette instance" silenceThisInstance: "Mettre cette instance en sourdine" operations: "Opérations" software: "Logiciel" +softwareName: "Nom du logiciel" version: "Version" metadata: "Métadonnées" withNFiles: "{n} fichier(s)" @@ -231,6 +238,9 @@ blockedInstances: "Instances bloquées" blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance." silencedInstances: "Instances mises en sourdine" silencedInstancesDescription: "Énumérer les noms d'hôte des instances à mettre en sourdine. Tous les comptes des instances énumérées seront traités comme mis en sourdine, ne peuvent faire que des demandes de suivi et ne peuvent pas mentionner les comptes locaux s'ils ne sont pas suivis. Cela n'affectera pas les instances bloquées." +mediaSilencedInstances: "Médias silencieux sur ces instances" +mediaSilencedInstancesDescription: "Liste des noms de serveurs où vous voulez que les médias soient silencieux, séparés par un retour à la ligne.\nTous les comptes des instances listées seront considérés comme sensibles, et ne peuvent pas utilisés d'émojis personnalisés. Ceci n'affectera pas les serveurs bloquées." +federationAllowedHosts: "Serveurs qui autorisent la fédération" muteAndBlock: "Masqué·e·s / Bloqué·e·s" mutedUsers: "Utilisateur·rice·s en sourdine" blockedUsers: "Utilisateur·rice·s bloqué·e·s" @@ -238,7 +248,6 @@ noUsers: "Il n’y a pas d’utilisateur·rice·s" editProfile: "Modifier votre profil" noteDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note ?" pinLimitExceeded: "Vous ne pouvez plus épingler d’autres notes." -intro: "L’installation de Misskey est terminée ! Veuillez créer un compte administrateur." done: "Terminé" processing: "Traitement en cours" preview: "Aperçu" @@ -760,7 +769,6 @@ thisIsExperimentalFeature: "Ceci est une fonctionnalité expérimentale. Il y a developer: "Développeur" makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"." makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra pas sur la page \"Découvrir\"." -showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline" duplicate: "Duliquer" left: "Gauche" center: "Centrer" @@ -1209,9 +1217,7 @@ showAvatarDecorations: "Afficher les décorations d'avatar" releaseToRefresh: "Relâcher pour rafraîchir" refreshing: "Rafraîchissement..." pullDownToRefresh: "Tirer vers le bas pour rafraîchir" -disableStreamingTimeline: "Désactiver les mises à jour en temps réel de la ligne du temps" useGroupedNotifications: "Grouper les notifications" -signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré." cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie." doReaction: "Réagir" code: "Code" @@ -1275,6 +1281,18 @@ pleaseSelectAccount: "Sélectionner un compte" availableRoles: "Rôles disponibles" postForm: "Formulaire de publication" information: "Informations" +inMinutes: "min" +inDays: "j" +widgets: "Widgets" +presets: "Préréglage" +_imageEditing: + _vars: + filename: "Nom du fichier" +_imageFrameEditor: + header: "Entête" + font: "Police de caractères" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "Inviter" noHistory: "Pas d'historique" @@ -1837,7 +1855,6 @@ _theme: buttonBg: "Arrière-plan du bouton" buttonHoverBg: "Arrière-plan du bouton (survolé)" inputBorder: "Cadre de la zone de texte" - driveFolderBg: "Arrière-plan du dossier de disque" badge: "Badge" messageBg: "Arrière plan de la discussion" fgHighlighted: "Texte mis en évidence" @@ -1998,6 +2015,14 @@ _widgets: _userList: chooseList: "Sélectionner une liste" birthdayFollowings: "Utilisateurs qui fêtent l'anniversaire aujourd'hui" +_widgetOptions: + height: "Hauteur" + _button: + colored: "Coloré" + _clock: + size: "Taille" + _birthdayFollowings: + period: "Durée" _cw: hide: "Masquer" show: "Afficher le contenu" @@ -2039,6 +2064,9 @@ _postForm: replyPlaceholder: "Répondre à cette note ..." quotePlaceholder: "Citez cette note ..." channelPlaceholder: "Publier au canal…" + _howToUse: + visibility_title: "Visibilité" + menu_title: "Menu" _placeholders: a: "Quoi de neuf ?" b: "Il s'est passé quelque chose ?" @@ -2336,9 +2364,6 @@ _dataSaver: _avatar: title: "Animation d'avatars" description: "Arrête l'animation d'avatars. Comme les images animées peuvent être plus volumineuses que les images normales, cela permet de réduire davantage le trafic de données." - _urlPreview: - title: "Vignettes d'aperçu des URL" - description: "Les vignettes d'aperçu des URL ne seront plus chargées." _code: title: "Mise en évidence du code" description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données." @@ -2367,3 +2392,25 @@ _search: searchScopeAll: "Tous" searchScopeLocal: "Local" searchScopeUser: "Spécifier l'utilisateur·rice" +_watermarkEditor: + driveFileTypeWarn: "Ce fichier n'est pas pris en charge" + opacity: "Transparence" + scale: "Taille" + text: "Texte" + position: "Position" + type: "Type" + image: "Images" + advanced: "Avancé" + angle: "Angle" +_imageEffector: + _fxProps: + angle: "Angle" + scale: "Taille" + size: "Taille" + offset: "Position" + color: "Couleur" + opacity: "Transparence" + lightness: "Clair" +_qr: + showTabTitle: "Affichage" + raw: "Texte" diff --git a/locales/generateDTS.js b/locales/generateDTS.js deleted file mode 100644 index 49807144ec..0000000000 --- a/locales/generateDTS.js +++ /dev/null @@ -1,230 +0,0 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import * as yaml from 'js-yaml'; -import ts from 'typescript'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const parameterRegExp = /\{(\w+)\}/g; - -function createMemberType(item) { - if (typeof item !== 'string') { - return ts.factory.createTypeLiteralNode(createMembers(item)); - } - const parameters = Array.from( - item.matchAll(parameterRegExp), - ([, parameter]) => parameter, - ); - return parameters.length - ? ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createUnionTypeNode( - parameters.map((parameter) => - ts.factory.createStringLiteral(parameter), - ), - ), - ], - ) - : ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); -} - -function createMembers(record) { - return Object.entries(record).map(([k, v]) => { - const node = ts.factory.createPropertySignature( - undefined, - ts.factory.createStringLiteral(k), - undefined, - createMemberType(v), - ); - if (typeof v === 'string') { - ts.addSyntheticLeadingComment( - node, - ts.SyntaxKind.MultiLineCommentTrivia, - `* - * ${v.replace(/\n/g, '\n * ')} - `, - true, - ); - } - return node; - }); -} - -export default function generateDTS() { - const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); - const members = createMembers(locale); - const elements = [ - ts.factory.createVariableStatement( - [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('kParameters'), - undefined, - ts.factory.createTypeOperatorNode( - ts.SyntaxKind.UniqueKeyword, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword), - ), - undefined, - ), - ], - ts.NodeFlags.Const, - ), - ), - ts.factory.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createTypeParameterDeclaration( - undefined, - ts.factory.createIdentifier('T'), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ), - ], - undefined, - [ - ts.factory.createPropertySignature( - undefined, - ts.factory.createComputedPropertyName( - ts.factory.createIdentifier('kParameters'), - ), - undefined, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('T'), - undefined, - ), - ), - ], - ), - ts.factory.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('ILocale'), - undefined, - undefined, - [ - ts.factory.createIndexSignature( - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('_'), - undefined, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - undefined, - ), - ], - ts.factory.createUnionTypeNode([ - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - ), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ILocale'), - undefined, - ), - ]), - ), - ], - ), - ts.factory.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('Locale'), - undefined, - [ - ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ - ts.factory.createExpressionWithTypeArguments( - ts.factory.createIdentifier('ILocale'), - undefined, - ), - ]), - ], - members, - ), - ts.factory.createVariableStatement( - [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('locales'), - undefined, - ts.factory.createTypeLiteralNode([ - ts.factory.createIndexSignature( - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('lang'), - undefined, - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.StringKeyword, - ), - undefined, - ), - ], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - ), - ]), - undefined, - ), - ], - ts.NodeFlags.Const, - ), - ), - ts.factory.createFunctionDeclaration( - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - undefined, - ts.factory.createIdentifier('build'), - undefined, - [], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - undefined, - ), - ts.factory.createExportDefault(ts.factory.createIdentifier('locales')), - ]; - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.MultiLineCommentTrivia, - ' eslint-disable ', - true, - ); - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.SingleLineCommentTrivia, - ' This file is generated by locales/generateDTS.js', - true, - ); - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.SingleLineCommentTrivia, - ' Do not edit this file directly.', - true, - ); - const printed = ts - .createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }) - .printList( - ts.ListFormat.MultiLine, - ts.factory.createNodeArray(elements), - ts.createSourceFile( - 'index.d.ts', - '', - ts.ScriptTarget.ESNext, - true, - ts.ScriptKind.TS, - ), - ); - - fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8'); -} diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 22ccbf153a..dbb5d63da6 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -5,9 +5,13 @@ introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersif poweredByMisskeyDescription: "{name} adalah sebuah layanan (instance) yang menggunakan platform sumber terbuka Misskey." monthAndDay: "{day} {month}" search: "Penelusuran" +reset: "Reset" notifications: "Notifikasi" username: "Nama Pengguna" password: "Kata sandi" +initialPasswordForSetup: "Kata sandi untuk memulai konfigurasi awal" +initialPasswordIsIncorrect: "Kata sandi untuk memulai konfigurasi awal salah." +initialPasswordForSetupDescription: "Jika Anda menginstal Misskey sendiri, gunakan kata sandi yang Anda masukkan di berkas konfigurasi.\nJika Anda menggunakan layanan hosting Misskey, gunakan kata sandi yang diberikan.\nJika Anda belum mengatur kata sandi, biarkan kosong dan lanjutkan." forgotPassword: "Lupa Kata Sandi" fetchingAsApObject: "Mengambil data dari Fediverse..." ok: "OK" @@ -45,6 +49,7 @@ pin: "Sematkan ke profil" unpin: "Lepas sematan dari profil" copyContent: "Salin konten" copyLink: "Salin tautan" +copyRemoteLink: "Salin tautan jarak jauh" copyLinkRenote: "Salin tautan renote" delete: "Hapus" deleteAndEdit: "Hapus dan sunting" @@ -212,8 +217,10 @@ perDay: "per Hari" stopActivityDelivery: "Berhenti mengirim aktivitas" blockThisInstance: "Blokir instansi ini" silenceThisInstance: "Senyapkan instansi ini" +mediaSilenceThisInstance: "Server media senyap" operations: "Tindakan" software: "Perangkat lunak" +softwareName: "Nama Perangkat Lunak" version: "Versi" metadata: "Metadata" withNFiles: "{n} berkas" @@ -233,6 +240,8 @@ blockedInstances: "Instansi terblokir" blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi ini." silencedInstances: "Instansi yang disenyapkan" silencedInstancesDescription: "Daftar nama host dari instansi yang ingin kamu senyapkan. Semua akun dari instansi yang terdaftar akan diperlakukan sebagai disenyapkan. Hal ini membuat akun hanya dapat membuat permintaan mengikuti, dan tidak dapat menyebutkan akun lokal apabila tidak mengikuti. Hal ini tidak akan mempengaruhi instansi yang diblokir." +mediaSilencedInstances: "Server dengan media dibisukan" +mediaSilencedInstancesDescription: "Masukkan host server yang medianya ingin Anda bisukan, pisahkan dengan baris baru. Semua berkas dari akun di server ini akan dianggap sebagai sensitif dan emoji kustom tidak akan tersedia. Ini tidak akan membengaruhi server yang diblokir." federationAllowedHosts: "Server yang membolehkan federasi" muteAndBlock: "Bisukan / Blokir" mutedUsers: "Pengguna yang dibisukan" @@ -241,7 +250,6 @@ noUsers: "Tidak ada pengguna" editProfile: "Sunting profil" noteDeleteConfirm: "Apakah kamu yakin ingin menghapus catatan ini?" pinLimitExceeded: "Kamu tidak dapat menyematkan catatan lagi" -intro: "Instalasi Misskey telah selesai! Mohon untuk membuat pengguna admin." done: "Selesai" processing: "Memproses" preview: "Pratinjau" @@ -292,6 +300,7 @@ uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesa explore: "Jelajahi" messageRead: "Telah dibaca" noMoreHistory: "Tidak ada sejarah lagi" +startChat: "Kirim pesan" nUsersRead: "Dibaca oleh {n}" agreeTo: "Saya setuju kepada {0}" agree: "Setuju" @@ -391,7 +400,7 @@ enableHcaptcha: "Nyalakan hCaptcha" hcaptchaSiteKey: "Site Key" hcaptchaSecretKey: "Secret Key" mcaptcha: "mCaptcha" -enableMcaptcha: "Nyalakan mCaptcha" +enableMcaptcha: "" mcaptchaSiteKey: "Site key" mcaptchaSecretKey: "Secret Key" mcaptchaInstanceUrl: "URL instansi mCaptcha" @@ -504,6 +513,7 @@ emojiStyle: "Gaya emoji" native: "Native" menuStyle: "Gaya menu" style: "Gaya" +popup: "Pemunculan" showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk" showReactionsCount: "Lihat jumlah reaksi dalam catatan" noHistory: "Tidak ada riwayat" @@ -560,6 +570,7 @@ showFixedPostForm: "Tampilkan form posting di atas lini masa" showFixedPostFormInChannel: "Tampilkan form posting di atas lini masa (Kanal)" withRepliesByDefaultForNewlyFollowed: "Termasuk balasan dari pengguna baru yang diikuti pada lini masa secara bawaan" newNoteRecived: "Kamu mendapat catatan baru" +newNote: "Catatan baru" sounds: "Bunyi" sound: "Bunyi" listen: "Dengarkan" @@ -761,7 +772,6 @@ thisIsExperimentalFeature: "Fitur ini eksperimental. Fungsionalitas dari fitur i developer: "Pengembang" makeExplorable: "Buat akun tampil di \"Jelajahi\"" makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di menu \"Jelajahi\"" -showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada lini masa" duplicate: "Duplikat" left: "Kiri" center: "Tengah" @@ -1023,6 +1033,7 @@ permissionDeniedError: "Operasi ditolak" permissionDeniedErrorDescription: "Akun ini tidak memiliki izin untuk melakukan aksi ini." preset: "Prasetel" selectFromPresets: "Pilih dari prasetel" +custom: "Penyesuaian" achievements: "Pencapaian" gotInvalidResponseError: "Respon peladen tidak valid" gotInvalidResponseErrorDescription: "Peladen tidak dapat dijangkau atau sedang dalam perawatan. Mohon coba lagi nanti." @@ -1042,7 +1053,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi disableFederationOk: "Matikan federasi" invitationRequiredToRegister: "Instansi ini dalam mode undangan-saja. Kamu harus memasukkan kode undangan yang valid untuk mendaftar." emailNotSupported: "Instansi ini tidak mendukung mengirim surel" -postToTheChannel: "Catat ke kanal" +postToTheChannel: "Buat Catatan ke Kanal" cannotBeChangedLater: "Hal ini nantinya tidak dapat diubah lagi." reactionAcceptance: "Penerimaan reaksi" likeOnly: "Hanya suka" @@ -1105,6 +1116,7 @@ preservedUsernamesDescription: "Daftar nama pengguna yang dicadangkan dipisah de createNoteFromTheFile: "Buat catatan dari berkas ini" archive: "Arsipkan" archived: "Diarsipkan" +unarchive: "Batalkan pengarsipan" channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?" channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi." thisChannelArchived: "Kanal ini telah diarsipkan." @@ -1206,9 +1218,7 @@ showAvatarDecorations: "Tampilkan dekorasi avatar" releaseToRefresh: "Lepaskan untuk memuat ulang" refreshing: "Sedang memuat ulang..." pullDownToRefresh: "Tarik ke bawah untuk memuat ulang" -disableStreamingTimeline: "Nonaktifkan pembaharuan lini masa real-time" useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan" -signupPendingError: "Terdapat masalah ketika memverifikasi alamat surel. Tautan kemungkinan telah kedaluwarsa." cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan." doReaction: "Tambahkan reaksi" code: "Kode" @@ -1248,6 +1258,7 @@ noDescription: "Tidak ada deskripsi" alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti" inquiry: "Hubungi kami" tryAgain: "Silahkan coba lagi." +sensitiveMediaRevealConfirm: "Media sensitif. Apakah ingin melihat?" createdLists: "Senarai yang dibuat" createdAntennas: "Antena yang dibuat" fromX: "Dari {x}" @@ -1255,18 +1266,52 @@ noteOfThisUser: "Catatan oleh pengguna ini" clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan." performance: "Kinerja" modified: "Diubah" +discard: "Buang" thereAreNChanges: "Ada {n} perubahan" +signinWithPasskey: "Masuk dengan kunci sandi" +unknownWebAuthnKey: "Kunci sandi tidak terdaftar." +passkeyVerificationFailed: "Verifikasi kunci sandi gagal." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "Verifikasi kunci sandi berhasil, namun pemasukan tanpa sandi dinonaktifkan." +messageToFollower: "Pesan kepada pengikut" prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" +lockdown: "Kuncitara" +noName: "Tidak ada nama" +skip: "Lewati" +paste: "Tempel" +emojiPalette: "Palet emoji" postForm: "Buat catatan" information: "Informasi" +chat: "Obrolan" +directMessage: "Obrolan pengguna" +right: "Kanan" +bottom: "Bawah" +top: "Atas" +advice: "Saran" +inMinutes: "menit" +inDays: "hari" +widgets: "Widget" +presets: "Prasetel" +_imageEditing: + _vars: + filename: "Nama berkas" +_imageFrameEditor: + header: "Header" + font: "Font" + fontSerif: "Serif" + fontSansSerif: "Sans-serif" _chat: invitations: "Undang" + history: "Riwayat obrolan" noHistory: "Tidak ada riwayat" members: "Anggota" home: "Beranda" send: "Kirim" + chatWithThisUser: "Obrolan pengguna" _settings: webhook: "Webhook" + contentsUpdateFrequency: "Frekuensi pembaruan konten" +_preferencesProfile: + profileName: "Nama profil" _abuseUserReport: accept: "Setuju" reject: "Tolak" @@ -1952,7 +1997,6 @@ _theme: buttonBg: "Latar belakang tombol" buttonHoverBg: "Latar belakang tombol (Mengambang)" inputBorder: "Batas bidang masukan" - driveFolderBg: "Latar belakang folder drive" badge: "Lencana" messageBg: "Latar belakang obrolan" fgHighlighted: "Teks yang disorot" @@ -1961,6 +2005,7 @@ _sfx: noteMy: "Catatan (Saya)" notification: "Notifikasi" reaction: "Ketika memilih reaksi" + chatMessage: "Obrolan pengguna" _soundSettings: driveFile: "Menggunakan berkas audio dalam Drive" driveFileWarn: "Pilih berkas audio dari Drive" @@ -2163,6 +2208,15 @@ _widgets: chooseList: "Pilih daftar" clicker: "Pengeklik" birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini" + chat: "Obrolan pengguna" +_widgetOptions: + height: "Tinggi" + _button: + colored: "Diwarnai" + _clock: + size: "Ukuran" + _birthdayFollowings: + period: "Durasi" _cw: hide: "Sembunyikan" show: "Lihat konten" @@ -2205,6 +2259,9 @@ _postForm: replyPlaceholder: "Balas ke catatan ini..." quotePlaceholder: "Kutip catatan ini..." channelPlaceholder: "Posting ke kanal" + _howToUse: + visibility_title: "Visibilitas" + menu_title: "Menu" _placeholders: a: "Sedang apa kamu saat ini?" b: "Apa yang terjadi di sekitarmu?" @@ -2404,13 +2461,14 @@ _deck: main: "Utama" widgets: "Widget" notifications: "Notifikasi" - tl: "Lini masa" + tl: "Beranda" antenna: "Antena" list: "Daftar" channel: "Kanal" mentions: "Sebutan" direct: "Langsung" roleTimeline: "Lini masa peran" + chat: "Obrolan pengguna" _dialog: charactersExceeded: "Kamu telah melebihi batas karakter maksimum! Saat ini pada {current} dari {max}." charactersBelow: "Kamu berada di bawah batas minimum karakter! Saat ini pada {current} dari {min}." @@ -2533,9 +2591,6 @@ _dataSaver: _avatar: title: "Gambar avatar" description: "Hentikan animasi gambar avatar. Gambar animasi dapat berukuran lebih besar dari gambar biasa, berpotensi pada pengurangan lalu lintas data lebih jauh." - _urlPreview: - title: "Gambar kecil URL pratinjau" - description: "Gambar kecil URL pratinjau tidak akan dimuat lagi." _code: title: "Penyorotan kode" description: "Jika notasi penyorotan kode digunakan di MFM, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data." @@ -2615,3 +2670,25 @@ _search: searchScopeAll: "Semua" searchScopeLocal: "Lokal" searchScopeUser: "Pengguna spesifik" +_watermarkEditor: + driveFileTypeWarn: "Berkas ini tidak didukung" + opacity: "Opasitas" + scale: "Ukuran" + text: "Teks" + position: "Posisi" + type: "Tipe" + image: "Gambar" + advanced: "Tingkat lanjut" + angle: "Sudut" +_imageEffector: + _fxProps: + angle: "Sudut" + scale: "Ukuran" + size: "Ukuran" + offset: "Posisi" + color: "Warna" + opacity: "Opasitas" + lightness: "Menerangkan" +_qr: + showTabTitle: "Tampilkan" + raw: "Teks" diff --git a/locales/index.js b/locales/index.js deleted file mode 100644 index 091d216dee..0000000000 --- a/locales/index.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Languages Loader - */ - -import * as fs from 'node:fs'; -import * as yaml from 'js-yaml'; - -const merge = (...args) => args.reduce((a, c) => ({ - ...a, - ...c, - ...Object.entries(a) - .filter(([k]) => c && typeof c[k] === 'object') - .reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {}) -}), {}); - -const languages = [ - 'ar-SA', - 'ca-ES', - 'cs-CZ', - 'da-DK', - 'de-DE', - 'en-US', - 'es-ES', - 'fr-FR', - 'id-ID', - 'it-IT', - 'ja-JP', - 'ja-KS', - 'kab-KAB', - 'kn-IN', - 'ko-KR', - 'nl-NL', - 'no-NO', - 'pl-PL', - 'pt-PT', - 'ru-RU', - 'sk-SK', - 'th-TH', - 'ug-CN', - 'uk-UA', - 'vi-VN', - 'zh-CN', - 'zh-TW', -]; - -const primaries = { - 'en': 'US', - 'ja': 'JP', - 'zh': 'CN', -}; - -// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く -const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); - -export function build() { - // vitestの挙動を調整するため、一度ローカル変数化する必要がある - // https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577 - // https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785 - const metaUrl = import.meta.url; - const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {}); - - // 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す - const removeEmpty = (obj) => { - for (const [k, v] of Object.entries(obj)) { - if (v === '') { - delete obj[k]; - } else if (typeof v === 'object') { - removeEmpty(v); - } - } - return obj; - }; - removeEmpty(locales); - - return Object.entries(locales) - .reduce((a, [k, v]) => (a[k] = (() => { - const [lang] = k.split('-'); - switch (k) { - case 'ja-JP': return v; - case 'ja-KS': - case 'en-US': return merge(locales['ja-JP'], v); - default: return merge( - locales['ja-JP'], - locales['en-US'], - locales[`${lang}-${primaries[lang]}`] ?? {}, - v - ); - } - })(), a), {}); -} - -export default build(); diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 75691d817f..47f2bbdb72 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -83,6 +83,8 @@ files: "Allegati" download: "Scarica" driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?" unfollowConfirm: "Vuoi davvero togliere il Following a {name}?" +cancelFollowRequestConfirm: "Vuoi annullare la tua richiesta di follow inviata a {name}?" +rejectFollowRequestConfirm: "Vuoi rifiutare la richiesta di follow ricevuta da {name}?" exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive." importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo." lists: "Liste" @@ -139,12 +141,12 @@ overwriteFromPinnedEmojis: "Sovrascrivi con le impostazioni globali" reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere." rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note" attachCancel: "Rimuovi allegato" -deleteFile: "File da Drive eliminato" +deleteFile: "Elimina un file dal Drive" markAsSensitive: "Segna come esplicito" unmarkAsSensitive: "Non segnare come esplicito " enterFileName: "Nome del file" mute: "Silenziare" -unmute: "Riattiva l'audio" +unmute: "Dai voce" renoteMute: "Silenziare le Rinota" renoteUnmute: "Non silenziare le Rinota" block: "Bloccare" @@ -220,6 +222,7 @@ silenceThisInstance: "Silenziare l'istanza" mediaSilenceThisInstance: "Silenzia i media dell'istanza" operations: "Operazioni" software: "Software" +softwareName: "Nome del software" version: "Versione" metadata: "Metadato" withNFiles: "{n} file in allegato" @@ -250,9 +253,9 @@ noUsers: "Non ci sono profili" editProfile: "Modifica profilo" noteDeleteConfirm: "Vuoi davvero eliminare questa Nota?" pinLimitExceeded: "Non puoi fissare altre note " -intro: "L'installazione di Misskey è terminata! Si prega di creare il profilo amministratore." done: "Fine" processing: "In elaborazione" +preprocessing: "In preparazione" preview: "Anteprima" default: "Predefinito" defaultValueIs: "Predefinito: {value}" @@ -298,8 +301,10 @@ uploadFromUrl: "Incolla URL immagine" uploadFromUrlDescription: "URL del file che vuoi caricare" uploadFromUrlRequested: "Caricamento richiesto" uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo." +uploadNFiles: "Caricare {n} file singolarmente" explore: "Esplora" messageRead: "Visualizzato" +readAllChatMessages: "Segna tutti i messaggi come già letti" noMoreHistory: "Non c'è più cronologia da visualizzare" startChat: "Inizia a chattare" nUsersRead: "Letto da {n} persone" @@ -326,11 +331,13 @@ dark: "Scuro" lightThemes: "Tema Chiaro" darkThemes: "Tema Scuro" syncDeviceDarkMode: "Sincronizza il tema scuro con le impostazioni del dispositivo" +switchDarkModeManuallyWhenSyncEnabledConfirm: "({x}) è attiva. Vuoi disattivare la sincronizzazione e passare alla modalità manuale?" drive: "Drive" fileName: "Nome dell'allegato" selectFile: "Scelta allegato" selectFiles: "Scelta allegato" selectFolder: "Seleziona cartella" +unselectFolder: "Deseleziona la cartella" selectFolders: "Seleziona cartella" fileNotSelected: "Nessun file selezionato" renameFile: "Rinomina file" @@ -343,6 +350,7 @@ addFile: "Allega" showFile: "Visualizza file" emptyDrive: "Il Drive è vuoto" emptyFolder: "La cartella è vuota" +dropHereToUpload: "Trascina qui il tuo file per caricarlo" unableToDelete: "Eliminazione impossibile" inputNewFileName: "Inserisci nome del nuovo file" inputNewDescription: "Inserisci una nuova descrizione" @@ -382,7 +390,7 @@ disconnectService: "Disconnetti" enableLocalTimeline: "Abilita la timeline locale" enableGlobalTimeline: "Abilita la timeline federata" disablingTimelinesInfo: "Anche disabilitandole, gli Amministratori e i Moderatori potranno comunque accedervi." -registration: "Iscriviti" +registration: "Registrazione" invite: "Invita" driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale" driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto" @@ -453,7 +461,7 @@ setupOf2fa: "Impostare l'autenticazione a due fattori" totp: "App di autenticazione a due fattori (2FA/MFA)" totpDescription: "Puoi autenticarti inserendo un codice OTP tramite la tua App di autenticazione a due fattori (2FA/MFA)" moderator: "Moderatore" -moderation: "moderazione" +moderation: "Moderazione" moderationNote: "Promemoria di moderazione" moderationNoteDescription: "Puoi scrivere promemoria condivisi solo tra moderatori." addModerationNote: "Aggiungi promemoria di moderazione" @@ -494,7 +502,7 @@ attachAsFileQuestion: "Il testo copiato eccede le dimensioni, vuoi allegarlo?" onlyOneFileCanBeAttached: "È possibile allegare al messaggio soltanto uno file" signinRequired: "Occorre avere un profilo registrato su questa istanza" signinOrContinueOnRemote: "Per continuare, devi accedere alla tua istanza o registrarti su questa e poi accedere" -invitations: "Invita" +invitations: "Inviti" invitationCode: "Codice di invito" checking: "Confermando" available: "Disponibile" @@ -520,7 +528,7 @@ style: "Stile" drawer: "Drawer" popup: "Popup" showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse" -showReactionsCount: "Visualizza il numero di reazioni su una nota" +showReactionsCount: "Visualizza la quantità di reazioni su una nota" noHistory: "Nessuna cronologia" signinHistory: "Storico degli accessi al profilo" enableAdvancedMfm: "Attivare i Misskey Flavoured Markdown (MFM) avanzati" @@ -575,10 +583,12 @@ showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timel showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline" withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita" newNoteRecived: "Nuove Note da leggere" +newNote: "Nuove Note" sounds: "Impostazioni suoni" sound: "Suono" +notificationSoundSettings: "Preferenze di notifica" listen: "Ascolta" -none: "Nessuno" +none: "Nessuna" showInPage: "Visualizza in pagina" popout: "Finestra pop-out" volume: "Volume" @@ -603,7 +613,7 @@ descendingOrder: "Diminuisce" scratchpad: "ScratchPad" scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey." uiInspector: "UI Inspector" -uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:." +uiInspectorDescription: "Puoi visualizzare un elenco di elementi grafici presenti in memoria. I componenti dell'interfaccia grafica vengono generati dalle funzioni Ui:C:." output: "Output" script: "Script" disablePagesScript: "Disabilitare AiScript nelle pagine" @@ -683,16 +693,16 @@ emptyToDisableSmtpAuth: "Lasciare i campi vuoti se non c'è autenticazione SMTP" smtpSecure: "Usare SSL/TLS implicito per le connessioni SMTP" smtpSecureInfo: "Disabilitare quando è attivo STARTTLS." testEmail: "Verifica il funzionamento" -wordMute: "Filtri parole" -wordMuteDescription: "Contrae le Note con la parola o la frase specificata. Permette di espandere le Note, cliccandole." -hardWordMute: "Filtro parole forte" +wordMute: "Parole silenziate" +wordMuteDescription: "Comprimi le Note che hanno la parola o la regola specificata. Cliccale per espanderle e leggerne comunque il contenuto." +hardWordMute: "Filtro per parole" showMutedWord: "Elenca le parole silenziate" -hardWordMuteDescription: "Nasconde le Note con la parola o la frase specificata. A differenza delle parole silenziate, la Nota non verrà federata." +hardWordMuteDescription: "Ignora le Note con la parola o la regola specificata. A differenza delle \"Parole Silenziate\", queste Note non ti verranno proprio recapitate." regexpError: "errore regex" regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:" instanceMute: "Silenziare l'istanza" userSaysSomething: "{name} ha detto qualcosa" -userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\"" +userSaysSomethingAbout: "{name} ha anNotato qualcosa su \"{word}\"" makeActive: "Attiva" display: "Visualizza" copy: "Copia" @@ -748,19 +758,19 @@ i18nInfo: "Misskey è tradotto in diverse lingue da volontari. Anche tu puoi con manageAccessTokens: "Gestisci token di accesso" accountInfo: "Informazioni profilo" notesCount: "Conteggio note" -repliesCount: "Numero di risposte inviate" -renotesCount: "Numero di note che hai ricondiviso" -repliedCount: "Numero di risposte ricevute" -renotedCount: "Numero delle tue note ricondivise" -followingCount: "Numero di Following" -followersCount: "Numero di profili che ti seguono" -sentReactionsCount: "Numero di reazioni inviate" -receivedReactionsCount: "Numero di reazioni ricevute" -pollVotesCount: "Numero di voti inviati" -pollVotedCount: "Numero di voti ricevuti" +repliesCount: "Quantità di risposte" +renotesCount: "Quantità di Note ricondivise" +repliedCount: "Quantità di risposte" +renotedCount: "Quantità di tue Note ricondivise" +followingCount: "Quantità di Following" +followersCount: "Quantità di Follower" +sentReactionsCount: "Quantità di reazioni" +receivedReactionsCount: "Quantità di reazioni ricevute" +pollVotesCount: "Quantità di voti" +pollVotedCount: "Quantità di voti ricevuti" yes: "Sì" no: "No" -driveFilesCount: "Numero di file nel Drive" +driveFilesCount: "Quantità di file nel Drive" driveUsage: "Utilizzazione del Drive" noCrawle: "Rifiuta l'indicizzazione dai robot." noCrawleDescription: "Richiedi che i motori di ricerca non indicizzino la tua pagina di profilo, le tue note, pagine, ecc." @@ -768,12 +778,13 @@ lockedAccountInfo: "A meno che non imposti la visibilità delle tue note su \"So alwaysMarkSensitive: "Segnare automaticamente come espliciti gli allegati" loadRawImages: "Visualizza le intere immagini allegate invece delle miniature." disableShowingAnimatedImages: "Disabilitare le immagini animate" +disableShowingAnimatedImages_caption: "L'attivazione delle animazioni immagini potrebbe interferire sull'accessibilità e sul risparmio energetico nel dispositivo." highlightSensitiveMedia: "Evidenzia i media espliciti" verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere al collegamento per compiere la verifica." notSet: "Non impostato" emailVerified: "Il tuo indirizzo email è stato verificato" noteFavoritesCount: "Conteggio note tra i preferiti" -pageLikesCount: "Numero di pagine che ti piacciono" +pageLikesCount: "Quantità di pagine che ti piacciono" pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\"" contact: "Contatti" useSystemFont: "Usa il carattere predefinito del sistema" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "Questa è una funzionalità sperimentale. Potrebbe e developer: "Sviluppatore" makeExplorable: "Profilo visibile pubblicamente nella pagina \"Esplora\"" makeExplorableDescription: "Disabilitando questa opzione, il tuo profilo non verrà elencato nella pagina \"Esplora\"." -showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline" duplicate: "Duplica" left: "Sinistra" center: "Centro" @@ -792,6 +802,7 @@ wide: "Largo" narrow: "Stretto" reloadToApplySetting: "Le tue preferenze verranno impostate dopo il ricaricamento della pagina. Vuoi ricaricare adesso?" needReloadToApply: "È necessario riavviare per rendere effettive le modifiche." +needToRestartServerToApply: "Per attivare le modifiche, occorre riavviare il server." showTitlebar: "Visualizza la barra del titolo" clearCache: "Svuota la cache" onlineUsersCount: "{n} persone attive adesso" @@ -818,9 +829,9 @@ currentVersion: "Versione attuale" latestVersion: "Ultima versione" youAreRunningUpToDateClient: "Stai usando la versione più recente del client." newVersionOfClientAvailable: "Una nuova versione del tuo client è disponibile." -usageAmount: "In uso" +usageAmount: "Quantità utilizzata" capacity: "Capacità" -inUse: "In uso" +inUse: "Usata da" editCode: "Modifica codice" apply: "Applica" receiveAnnouncementFromInstance: "Ricevi i messaggi informativi dall'istanza" @@ -895,7 +906,7 @@ useBlurEffect: "Utilizza effetto sfocatura" learnMore: "Per saperne di più" misskeyUpdated: "Misskey è stato aggiornato!" whatIsNew: "Informazioni sull'aggiornamento" -translate: "Traduci" +translate: "Traduzione" translatedFrom: "Traduzione da {x}" accountDeletionInProgress: "È in corso l'eliminazione del profilo" usernameInfo: "Un nome per identificare univocamente il tuo profilo sull'istanza. Puoi utilizzare caratteri alfanumerici maiuscoli, minuscoli e il trattino basso (_). Non potrai cambiare nome utente in seguito." @@ -906,7 +917,7 @@ pubSub: "Publish/Subscribe del profilo" lastCommunication: "La comunicazione più recente" resolved: "Risolto" unresolved: "Non risolto" -breakFollow: "Rimuovi Follower" +breakFollow: "Rimuovere Follower" breakFollowConfirm: "Vuoi davvero togliere questo Follower?" itsOn: "Abilitato" itsOff: "Disabilitato" @@ -939,7 +950,7 @@ tablet: "Tablet" auto: "Automatico" themeColor: "Colore del tema" size: "Dimensioni" -numberOfColumn: "Numero di colonne" +numberOfColumn: "Quantità di colonne" searchByGoogle: "Cerca" instanceDefaultLightTheme: "Istanza, tema luminoso predefinito." instanceDefaultDarkTheme: "Istanza, tema scuro predefinito." @@ -976,7 +987,7 @@ isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal s typeToConfirm: "Digita {x} per continuare" deleteAccount: "Eliminazione profilo" document: "Documentazione" -numberOfPageCache: "Numero di pagine cache" +numberOfPageCache: "Quantità di pagine in cache" numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria." logoutConfirm: "Vuoi davvero uscire da Misskey? " logoutWillClearClientData: "All'uscita, la configurazione del client viene rimossa dal browser. Per ripristinarla quando si effettua nuovamente l'accesso, abilitare il backup automatico." @@ -998,6 +1009,7 @@ failedToUpload: "errore di caricamento" cannotUploadBecauseInappropriate: "Non è possibile caricarlo perché è stato stabilito che potrebbe contenere contenuti inappropriati." cannotUploadBecauseNoFreeSpace: "Impossibile caricare a causa della mancanza di spazio libero sul drive." cannotUploadBecauseExceedsFileSizeLimit: "Il file non può essere caricato perché eccede le dimensioni consentite." +cannotUploadBecauseUnallowedFileType: "Impossibile caricare a causa di un tipo file non autorizzato." beta: "Versione beta" enableAutoSensitive: "Determinazione automatica del NSFW" enableAutoSensitiveDescription: "Se disponibile, il flag NSFW viene impostato automaticamente sui media utilizzando l'apprendimento automatico. Anche se questa funzione è disattivata, in alcuni casi può essere impostata automaticamente." @@ -1013,6 +1025,9 @@ pushNotificationAlreadySubscribed: "Le notifiche push sono già attivate" pushNotificationNotSupported: "Il client o il server non supporta le notifiche push" sendPushNotificationReadMessage: "Eliminare le notifiche push dopo la relativa lettura" sendPushNotificationReadMessageCaption: "Se possibile, verrà mostrata brevemente una notifica con il testo \"{emptyPushNotificationMessage}\". Potrebbe influire negativamente sulla durata della batteria." +pleaseAllowPushNotification: "Per favore, acconsenti alla ricezione di notifiche nel browser" +browserPushNotificationDisabled: "Non è stato possibile ottenere il consenso alla ricezione di notifche" +browserPushNotificationDisabledDescription: "Non hai concesso a {serverName} di spedire notifiche. Per favore, acconsenti alla ricezione nelle impostazioni del browser e riprova." windowMaximize: "Ingrandisci" windowMinimize: "Contrai finestra" windowRestore: "Ripristina" @@ -1023,7 +1038,7 @@ cannotLoad: "Caricamento impossibile" numberOfProfileView: "Visualizzazioni profilo" like: "Mi piace!" unlike: "Non mi piace" -numberOfLikes: "Numero di Like" +numberOfLikes: "Quantità di Like" show: "Visualizza" neverShow: "Non mostrare più" remindMeLater: "Rimanda" @@ -1049,6 +1064,7 @@ permissionDeniedError: "Errore, attività non autorizzata" permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione." preset: "Preimpostato" selectFromPresets: "Seleziona preimpostato" +custom: "Personalizzato" achievements: "Conquiste" gotInvalidResponseError: "Risposta del server non valida" gotInvalidResponseErrorDescription: "Il server potrebbe essere irraggiungibile o in manutenzione. Riprova più tardi." @@ -1087,6 +1103,7 @@ prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (qu hiddenTags: "Hashtag nascosti" hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga." notesSearchNotAvailable: "Non è possibile cercare tra le Note." +usersSearchNotAvailable: "La ricerca profili non è disponibile." license: "Licenza" unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?" myClips: "Le mie Clip" @@ -1161,12 +1178,13 @@ installed: "Installazione avvenuta" branding: "Branding" enableServerMachineStats: "Pubblicare le informazioni sul server" enableIdenticonGeneration: "Generazione automatica delle Identicon" +showRoleBadgesOfRemoteUsers: "Visualizza i badge per i ruoli concessi ai profili remoti" turnOffToImprovePerformance: "Disattiva, per migliorare le prestazioni" createInviteCode: "Genera codice di invito" createWithOptions: "Genera con opzioni" createCount: "Conteggio inviti" inviteCodeCreated: "Inviti generati" -inviteLimitExceeded: "Hai raggiunto il numero massimo di codici invito generabili." +inviteLimitExceeded: "Hai raggiunto la quantità massima di codici invito generabili." createLimitRemaining: "Inviti generabili: {limit} rimanenti" inviteLimitResetCycle: "Alle {time}, il limite verrà ripristinato a {limit}" expirationDate: "Scadenza" @@ -1193,7 +1211,7 @@ replies: "Risposte" renotes: "Rinota" loadReplies: "Leggi le risposte" loadConversation: "Leggi la conversazione" -pinnedList: "Elenco in primo piano" +pinnedList: "Lista in primo piano" keepScreenOn: "Mantenere lo schermo acceso" verifiedLink: "Abbiamo confermato la validità di questo collegamento" notifyNotes: "Notifica nuove Note" @@ -1237,9 +1255,8 @@ showAvatarDecorations: "Mostra decorazione della foto profilo" releaseToRefresh: "Rilascia per aggiornare" refreshing: "Aggiornamento..." pullDownToRefresh: "Trascinare per aggiornare" -disableStreamingTimeline: "Disabilitare gli aggiornamenti della TL in tempo reale" useGroupedNotifications: "Mostra le notifiche raggruppate" -signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." +emailVerificationFailedError: "La verifica dell'indirizzo e-mail non è andata a buon fine. Il link potrebbe essere scaduto." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." doReaction: "Reagisci" code: "Codice" @@ -1284,7 +1301,7 @@ sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?" createdLists: "Liste create" createdAntennas: "Antenne create" fromX: "Da {x}" -genEmbedCode: "Ottieni il codice di incorporamento" +genEmbedCode: "Ottieni il codice per incorporare" noteOfThisUser: "Elenco di Note di questo profilo" clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip." performance: "Prestazioni" @@ -1309,13 +1326,15 @@ availableRoles: "Ruoli disponibili" acknowledgeNotesAndEnable: "Attivare dopo averne compreso il comportamento." federationSpecified: "Questo server è federato solo con istanze specifiche del Fediverso. Puoi interagire solo con quelle scelte dall'amministrazione." federationDisabled: "Questo server ha la federazione disabilitata. Non puoi interagire con profili provenienti da altri server." +draft: "Bozza" +draftsAndScheduledNotes: "Bozze e Note pianificate" confirmOnReact: "Confermare le reazioni" reactAreYouSure: "Vuoi davvero reagire con {emoji} ?" markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?" unmarkAsSensitiveConfirm: "Vuoi davvero indicare come non esplicito il contenuto multimediale?" preferences: "Preferenze" accessibility: "Accessibilità" -preferencesProfile: "Profilo preferenze" +preferencesProfile: "Preferenze del profilo" copyPreferenceId: "Copia ID preferenze" resetToDefaultValue: "Ripristina a predefinito" overrideByAccount: "Sovrascrivere col profilo" @@ -1326,18 +1345,21 @@ restore: "Ripristina" syncBetweenDevices: "Sincronizzazione tra i dispositivi" preferenceSyncConflictTitle: "Sul server esiste già il valore impostato" preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?" +preferenceSyncConflictChoiceMerge: "Integra" preferenceSyncConflictChoiceServer: "Valore del server" preferenceSyncConflictChoiceDevice: "Valore del dispositivo" preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione" paste: "Incolla" emojiPalette: "Tavolozza emoji" postForm: "Finestra di pubblicazione" -textCount: "Il numero di caratteri" +textCount: "Quantità di caratteri" information: "Informazioni" chat: "Chat" +directMessage: "Chattare insieme" +directMessage_short: "Messaggio" migrateOldSettings: "Migrare le vecchie impostazioni" migrateOldSettings_description: "Di solito, viene fatto automaticamente. Se per qualche motivo non fossero migrate con successo, è possibile avviare il processo di migrazione manualmente, sovrascrivendo le configurazioni attuali." -compress: "Comprimi" +compress: "Compressione" right: "Destra" bottom: "Sotto" top: "Sopra" @@ -1345,7 +1367,100 @@ embed: "Incorporare" settingsMigrating: "Migrazione delle impostazioni. Attendere prego ... (Puoi anche migrare manualmente in un secondo momento, nel menu: Impostazioni → Altro → Migrazione delle impostazioni)" readonly: "Sola lettura" goToDeck: "Torna al Deck" +federationJobs: "Coda di federazione" +driveAboutTip: "Il Drive mostra l'elenco di file caricati in passato. Puoi organizzarli in cartelle, riusarli allegandoli ad altre note, o caricarli in anticipo e poi pubblicarli in un secondo momento. Tieni presente che se elimini un file, non sarà più visibile in nessuno degli oggetti a cui è allegato (Note, pagine, avatar, banner, ecc.)" +scrollToClose: "Scorri per chiudere" +advice: "Consiglio" +realtimeMode: "Modalità in tempo reale" +turnItOn: "Attivare" +turnItOff: "Disattivare" +emojiMute: "Silenzia emoji" +emojiUnmute: "De silenzia emoji" +muteX: "Silenzia {x}" +unmuteX: "De silenzia {x}" +abort: "Annulla" +tip: "Suggerimento" +redisplayAllTips: "Mostra tutti i suggerimenti" +hideAllTips: "Nascondi tutti i suggerimenti" +defaultImageCompressionLevel: "Livello predefinito di compressione immagini" +defaultImageCompressionLevel_description: "La compressione diminuisce la qualità dell'immagine, poca compressione mantiene alta qualità delle immagini. Aumentandola, si riducono le dimensioni del file, a discapito della qualità dell'immagine." +defaultCompressionLevel: "Compressione predefinita" +defaultCompressionLevel_description: "Diminuisci per mantenere la qualità aumentando le dimensioni del file.
Aumenta per ridurre le dimensioni del file e anche la qualità." +inMinutes: "min" +inDays: "giorni" +safeModeEnabled: "La modalità sicura è attiva" +pluginsAreDisabledBecauseSafeMode: "Tutti i plugin sono disattivati, poiché la modalità sicura è attiva." +customCssIsDisabledBecauseSafeMode: "Il CSS personalizzato non è stato applicato, poiché la modalità sicura è attiva." +themeIsDefaultBecauseSafeMode: "Quando la modalità sicura è attiva, viene utilizzato il tema predefinito. Quando la modalità sicura viene disattivata, il tema torna a essere quello precedente." +thankYouForTestingBeta: "Grazie per la tua collaborazione nella verifica delle versioni beta!" +createUserSpecifiedNote: "Crea Nota privata" +schedulePost: "Pianificare la pubblicazione" +scheduleToPostOnX: "Pianificare la pubblicazione {x}" +scheduledToPostOnX: "Pubblicazione pianificata {x}" +schedule: "Pianificare" +scheduled: "Pianificata" +widgets: "Riquadri" +deviceInfo: "Informazioni sul dispositivo" +deviceInfoDescription: "Se ci contatti per ricevere supporto tecnico, ti preghiamo di includere le seguenti informazioni per aiutarci a risolvere il tuo problema." +youAreAdmin: "Sei un amministratore" +frame: "Cornice" +presets: "Preimpostato" +zeroPadding: "Al vivo" +nothingToConfigure: "Niente da configurare" +_imageEditing: + _vars: + caption: "Didascalia dell'immagine" + filename: "Nome dell'allegato" + filename_without_ext: "Nome file senza estensione" + year: "Anno di scatto" + month: "Mese dello scatto" + day: "Giorno dello scatto" + hour: "Ora dello scatto" + minute: "Minuto dello scatto" + second: "Secondi dello scatto" + camera_model: "Modello di fotocamera" + camera_lens_model: "Modello della lente" + camera_mm: "Lunghezza focale" + camera_mm_35: "Lunghezza focale (equivalente a 35 mm)" + camera_f: "Diaframma" + camera_s: "Velocità otturatore" + camera_iso: "Sensibilità ISO" + gps_lat: "Latitudine" + gps_long: "Longitudine" +_imageFrameEditor: + title: "Modifica fotogramma" + tip: "Puoi decorare le immagini aggiungendo etichette con cornici e metadati." + header: "Intestazione" + footer: "Piè di pagina" + borderThickness: "Larghezza del bordo" + labelThickness: "Spessore etichetta" + labelScale: "Dimensione etichetta" + centered: "Allinea al centro" + captionMain: "Didascalia (grande)" + captionSub: "Didascalia (piccola)" + availableVariables: "Variabili disponibili" + withQrCode: "QR Code" + backgroundColor: "Colore dello sfondo" + textColor: "Colore del testo" + font: "Tipo di carattere" + fontSerif: "Serif" + fontSansSerif: "Sans serif" + quitWithoutSaveConfirm: "Uscire senza salvare?" + failedToLoadImage: "Impossibile caricare l'immagine" +_compression: + _quality: + high: "Alta qualità" + medium: "Media qualità" + low: "Bassa qualità" + _size: + large: "Taglia grande" + medium: "Taglia media" + small: "Taglia piccola" +_order: + newest: "Più recenti" + oldest: "Meno recenti" _chat: + messages: "Messaggi" noMessagesYet: "Ancora nessun messaggio" newMessage: "Nuovo messaggio" individualChat: "Chat individuale" @@ -1356,12 +1471,12 @@ _chat: inviteUserToChat: "Invita a chattare altre persone" yourRooms: "Le tue stanze" joiningRooms: "Stanze a cui partecipi" - invitations: "Invita" + invitations: "Inviti" noInvitations: "Nessun invito" history: "Cronologia" noHistory: "Nessuna cronologia" noRooms: "Nessuna stanza" - inviteUser: "Invita" + inviteUser: "Invita persona" sentInvitations: "Inviti spediti" join: "Entra" ignore: "Ignora" @@ -1378,6 +1493,8 @@ _chat: chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona." cannotChatWithTheUser: "Impossibile chattare con questa persona" cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo." + youAreNotAMemberOfThisRoomButInvited: "Non partecipi a questa stanza di chat, ma hai ricevuto un invito. Per partecipare, accetta l'invito." + doYouAcceptInvitation: "Intendi accettare l'invito?" chatWithThisUser: "Chatta con questa persona" thisUserAllowsChatOnlyFromFollowers: "Questa persona permette di chattare soltanto i propri Follower." thisUserAllowsChatOnlyFromFollowing: "Questa persona permette di chattare soltanto ai suoi Follow." @@ -1417,10 +1534,26 @@ _settings: makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile" makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni." useStickyIcons: "Fissa le icone durante lo scorrimento" + enableHighQualityImagePlaceholders: "Mostra un segnaposto per immagini in alta qualità" + uiAnimations: "Animazione dell'interfaccia" showNavbarSubButtons: "Mostra i pulsanti secondari nella barra di navigazione" ifOn: "Quando attivato" ifOff: "Quando disattivato" enableSyncThemesBetweenDevices: "Sincronizzare il tema tra i dispositivi" + enablePullToRefresh: "Scorri e aggiorna" + enablePullToRefresh_description: "Clicca col mouse e gira la rotella." + realtimeMode_description: "Connette al server e aggiorna il contenuto in tempo reale. Potrebbe aumentare l'uso dei dati e il consumo della batteria." + contentsUpdateFrequency: "Frequenza di ricezione contenuti" + contentsUpdateFrequency_description: "Se l'impostazione è alta, verranno aggiornati più frequentemente, consumando più dati e più batteria." + contentsUpdateFrequency_description2: "Quando la modalità è in tempo reale, arriveranno a prescindere." + showUrlPreview: "Mostra anteprima dell'URL" + showAvailableReactionsFirstInNote: "Mostra le reazioni disponibili in alto" + showPageTabBarBottom: "Visualizza le schede della pagina nella parte inferiore" + emojiPaletteBanner: "Puoi salvare i le emoji predefinite da appuntare in alto nel raccoglitore emoji come tavolozza e personalizzare in che modo visualizzare il raccoglitore." + enableAnimatedImages: "Attivare le immagini animate" + settingsPersistence_title: "Configurazione persistente" + settingsPersistence_description1: "Attivando le impostazioni persistenti si può evitare di riconfigurare il client successivamente." + settingsPersistence_description2: "Potrebbe non essere possibile attivare, dipende dall'ambiente." _chat: showSenderName: "Mostra il nome del mittente" sendOnEnter: "Invio spedisce" @@ -1428,6 +1561,9 @@ _preferencesProfile: profileName: "Nome del profilo" profileNameDescription: "Impostare il nome che indentifica questo dispositivo." profileNameDescription2: "Es: \"PC principale\" o \"Cellulare\"" + manageProfiles: "Gestione profili" + shareSameProfileBetweenDevicesIsNotRecommended: "Si sconsiglia di condividere lo stesso profilo su più dispositivi." + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "Se intendi sincronizzare solo alcuni parametri di configurazione su più dispositivi, devi attivare l'opzione \"Sincronizzazione tra dispositivi\" per ogni parametro interessato." _preferencesBackup: autoBackup: "Backup automatico" restoreFromBackup: "Ripristinare da backup" @@ -1437,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "Per abilitare i backup automatici, è necessario indicare il nome del profilo." autoPreferencesBackupIsNotEnabledForThisDevice: "Su questo dispositivo non è stato attivato il backup automatico delle preferenze." backupFound: "Esiste il Backup delle preferenze" + forceBackup: "Backup forzato delle impostazioni" _accountSettings: requireSigninToViewContents: "Per vedere il contenuto, è necessaria l'iscrizione" requireSigninToViewContentsDescription1: "Richiedere l'iscrizione per visualizzare tutte le Note e gli altri contenuti che hai creato. Probabilmente l'effetto è impedire la raccolta di informazioni da parte dei bot crawler." @@ -1466,6 +1603,7 @@ _delivery: manuallySuspended: "Sospesa manualmente" goneSuspended: "Sospensione server a causa dell'eliminazione" autoSuspendedForNotResponding: "Sospensione del server a causa di mancata risposta" + softwareSuspended: "Attualmente non disponibile perché il software non è più distribuito" _bubbleGame: howToPlay: "Come giocare" hold: "Tieni" @@ -1592,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "Elaborazione dati alternativa" fanoutTimelineDbFallbackDescription: "Attivando l'elaborazione alternativa, verrà interrogato ulteriormente il database se la timeline non è nella cache. \nDisattivando, si può ridurre ulteriormente il carico del server, evitando l'elaborazione alternativa, ma limitando l'intervallo recuperabile delle timeline." reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis." + remoteNotesCleaning: "Pulizia automatica dei contenuti remoti" + remoteNotesCleaning_description: "Se abilitata, verranno periodicamente rimosse le vecchie Note remote senza relazioni, per ridurre il sovraccarico del sistema." + remoteNotesCleaningMaxProcessingDuration: "Durata massima del processo di pulizia" + remoteNotesCleaningExpiryDaysForEachNotes: "Periodo minimo di conservazione delle note" inquiryUrl: "URL di contatto" inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." openRegistration: "Registrazioni aperte" openRegistrationWarning: "L’apertura della registrazione comporta dei rischi. Ti consigliamo di attivarla solo se hai predisposto il monitoraggio continuo del tuo server e puoi rispondere immediatamente se si verifica un problema." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Per prevenire SPAM, questa impostazione verrà disattivata automaticamente, se non si rileva alcuna attività di moderazione durante un certo periodo di tempo." + deliverSuspendedSoftware: "Software fuori produzione" + deliverSuspendedSoftwareDescription: "A causa di vulnerabilità o altri motivi, puoi interrompere la distribuzione di un software da un server specificandone il nome e la versione. Le informazioni sono fornite dall'altro server e l'autenticità non è garantita. Puoi indicare un intervallo di versione semantica, ma specificando >= 2024.3.1 non verranno incluse le versioni personalizzate come ad esempio 2024.3.1-custom.0, pertanto ti consigliamo di specificare una versione come >= 2024.3.1-0." + singleUserMode: "Modalità utenza singola" + singleUserMode_description: "Se sei l'unica persona a utilizzare questo server, l'abilitazione di questa modalità ottimizzerà le prestazioni." + signToActivityPubGet: "Firma delle richieste GET" + signToActivityPubGet_description: "Normalmente questa opzione dovrebbe essere abilitata. Se si verificano problemi con la comunicazione federata, disabilitarla potrebbe migliorare la situazione, ma d'altro canto potrebbe rendere impossibile la comunicazione, a seconda del server." + proxyRemoteFiles: "Proxy di file remoti" + proxyRemoteFiles_description: "Se abilitato, i file remoti verranno serviti tramite proxy. Utile per generare miniature delle immagini e proteggere la privacy degli utenti." + allowExternalApRedirect: "Consenti reindirizzamenti per le query tramite ActivityPub" + allowExternalApRedirect_description: "Se abilitata, consente ad altri server di interrogare contenuti di terze parti tramite il tuo server, con conseguente potenziale falsificazione dei contenuti." + userGeneratedContentsVisibilityForVisitor: "Visibilità dei contenuti generati dagli utenti ai non utenti" + userGeneratedContentsVisibilityForVisitor_description: "Questa funzionalità è utile per impedire che contenuti remoti inappropriati e difficili da moderare vengano inavvertitamente resi pubblici su Internet tramite il proprio server." + userGeneratedContentsVisibilityForVisitor_description2: "Esistono dei rischi nell'esporre incondizionatamente su internet tutto il contenuto del tuo server, incluso il contenuto remoto ricevuto da altri server. In particolare, occorre prestare attenzione, perché le persone non consapevoli della federazione potrebbero erroneamente credere che il contenuto remoto sia stato invece creato all'interno del proprio server." + restartServerSetupWizardConfirm_title: "Vuoi ripetere la procedura guidata di configurazione iniziale del server?" + restartServerSetupWizardConfirm_text: "Verranno ripristinate alcune tue impostazioni personalizzate." + entrancePageStyle: "Stile della pagina di ingresso" + showTimelineForVisitor: "Mostra la Timeline a visitatori non autenticati" + showActivitiesForVisitor: "Mostrare la propria attività" + _userGeneratedContentsVisibilityForVisitor: + all: "Tutto pubblico" + localOnly: "Pubblica solo contenuti locali, mantieni privati ​​i contenuti remoti" + none: "Tutto privato" _accountMigration: moveFrom: "Migra un altro profilo dentro a questo" moveFromSub: "Crea un alias verso un altro profilo remoto" @@ -1914,6 +2078,8 @@ _role: canManageCustomEmojis: "Gestire le emoji personalizzate" canManageAvatarDecorations: "Gestisce le decorazioni di immagini del profilo" driveCapacity: "Capienza del Drive" + maxFileSize: "Dimensione massima del file caricabile" + maxFileSize_caption: "Potrebbero esserci altre impostazioni nella fase precedente, come reverse proxy o CDN." alwaysMarkNsfw: "Impostare sempre come esplicito (NSFW)" canUpdateBioMedia: "Può aggiornare foto profilo e di testata" pinMax: "Quantità massima di Note in primo piano" @@ -1928,6 +2094,7 @@ _role: descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più." canHideAds: "Nascondere i banner" canSearchNotes: "Ricercare nelle Note" + canSearchUsers: "Può cercare profili" canUseTranslator: "Tradurre le Note" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" canImportAntennas: "Può importare Antenne" @@ -1936,6 +2103,12 @@ _role: canImportMuting: "Può importare Silenziati" canImportUserLists: "Può importare liste di Profili" chatAvailability: "Chat consentita" + uploadableFileTypes: "Tipi di file caricabili" + uploadableFileTypes_caption: "Specifica il tipo MIME. Puoi specificare più valori separandoli andando a capo, oppure indicare caratteri jolly con un asterisco (*). Ad esempio: image/*" + uploadableFileTypes_caption2: "A seconda del file, il tipo potrebbe non essere determinato. Se si desidera consentire tali file, aggiungere {x} alla specifica." + noteDraftLimit: "Numero massimo di Note in bozza, lato server" + scheduledNoteLimit: "Quantità di Note pianificabili contemporaneamente" + watermarkAvailable: "Disponibilità della funzione filigrana" _condition: roleAssignedTo: "Assegnato a ruoli manualmente" isLocal: "Profilo locale" @@ -2095,6 +2268,7 @@ _theme: install: "Installa un tema" manage: "Gestione dei temi" code: "Codice tema" + copyThemeCode: "Copia il codice del Tema" description: "Descrizione" installed: "{name} è installato" installedThemes: "Temi installati" @@ -2138,7 +2312,7 @@ _theme: hashtag: "Hashtag" mention: "Menzioni" mentionMe: "Menzioni (di me)" - renote: "Renota" + renote: "Rinota" modalBg: "Sfondo modale." divider: "Interruzione di linea" scrollbarHandle: "Maniglie della barra di scorrimento" @@ -2153,7 +2327,6 @@ _theme: buttonBg: "Sfondo del pulsante" buttonHoverBg: "Sfondo del pulsante (sorvolato)" inputBorder: "Inquadra casella di testo" - driveFolderBg: "Sfondo della cartella di disco" badge: "Distintivo" messageBg: "Sfondo della chat" fgHighlighted: "Testo in evidenza." @@ -2183,18 +2356,19 @@ _ago: yearsAgo: "{n} anni fa" invalid: "Niente da visualizzare" _timeIn: - seconds: "Dopo {n} secondi" - minutes: "Dopo {n} minuti" - hours: "Dopo {n} ore" - days: "Dopo {n} giorni" - weeks: "Dopo {n} settimane" - months: "Dopo {n} mesi" - years: "Dopo {n} anni" + seconds: "Tra {n} secondi" + minutes: "Tra {n} minuti" + hours: "Tra {n} ore" + days: "Tra {n} giorni" + weeks: "Tra {n} settimane" + months: "Tra {n} mesi" + years: "Tra {n} anni" _time: second: "s" minute: "min" hour: "ore" day: "giorni" + month: "Mese" _2fa: alreadyRegistered: "La configurazione è stata già completata." registerTOTP: "Registra una App di autenticazione a due fattori (2FA/MFA)" @@ -2324,6 +2498,7 @@ _auth: scopeUser: "Sto funzionando per il seguente profilo" pleaseLogin: "Per favore accedi al tuo account per cambiare i permessi dell'applicazione" byClickingYouWillBeRedirectedToThisUrl: "Consentendo l'accesso, si verrà reindirizzati presso questo indirizzo URL" + alreadyAuthorized: "Questa applicazione è già autorizzata ad accedere." _antennaSources: all: "Tutte le note" homeTimeline: "Note dai tuoi Following" @@ -2369,7 +2544,45 @@ _widgets: chooseList: "Seleziona una lista" clicker: "Cliccheria" birthdayFollowings: "Compleanni del giorno" - chat: "Chat" + chat: "Chatta con questa persona" +_widgetOptions: + showHeader: "Mostra la testata" + transparent: "Sfondo trasparente" + height: "Altezza" + _button: + colored: "Colorato" + _clock: + size: "Dimensioni" + thickness: "Spessore lancette" + thicknessThin: "Sottili" + thicknessMedium: "Medie" + thicknessThick: "Larghe" + graduations: "Quadrante" + graduationDots: "Punti" + graduationArabic: "Numeri" + fadeGraduations: "Sfumatura" + sAnimation: "Animazione dei secondi" + sAnimationElastic: "Realistica" + sAnimationEaseOut: "Morbida" + twentyFour: "Formato 24 ore" + labelTime: "Orario" + labelTz: "Fuso orario" + labelTimeAndTz: "Orario e fuso orario" + timezone: "Fuso orario" + showMs: "Millisecondi visibili" + showLabel: "Etichetta visibile" + _jobQueue: + sound: "Emetti un suono" + _rss: + url: "URL del Feed RSS" + refreshIntervalSec: "Intervallo di aggiornamento (in secondi)" + maxEntries: "Quantità massima visualizzabile" + _rssTicker: + shuffle: "Ordine casuale" + duration: "Velocità di scorrimento del ticker (in secondi)" + reverse: "Direzione inversa" + _birthdayFollowings: + period: "Durata" _cw: hide: "Nascondere" show: "Continua la lettura..." @@ -2406,12 +2619,28 @@ _visibility: followersDescription: "Visibile solo ai tuoi follower" specified: "Nota diretta" specifiedDescription: "Visibile solo ai profili menzionati" - disableFederation: "Senza federazione" + disableFederation: "Gestisci la federazione" disableFederationDescription: "Non spedire attività alle altre istanze remote" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Alcuni file non sono stati caricati. Vuoi annullare l'operazione?" + uploaderTip: "Il file non è ancora stato caricato. Nel menu file (tre puntini), puoi ritagliare l'immagine, mettere la filigrana, decidere la presenza o l'assenza di compressione... Il file verrà caricato automaticamente quando pubblichi la Nota." replyPlaceholder: "Rispondi a questa nota..." quotePlaceholder: "Cita questa nota..." channelPlaceholder: "Pubblica sul canale..." + showHowToUse: "Mostra il tutorial" + _howToUse: + content_title: "Testo" + content_description: "Inserisci il contenuto che desideri pubblicare." + toolbar_title: "Barra degli Strumenti" + toolbar_description: "Puoi allegare file e sondaggi, aggiungere Note, hashtag, inserire emoji e menzioni." + account_title: "Menu profilo" + account_description: "Puoi cambiare il profilo col quale vuoi pubblicare, elencare bozze e pianificare le Note." + visibility_title: "Visibilità" + visibility_description: "Puoi impostare il grado di visibilità delle Note." + menu_title: "Menù" + menu_description: "Puoi svolgere varie azioni, come salvare in bozza, pianificare le annotazioni, regolare le reazioni ricevute e altro." + submit_title: "Bottone invia" + submit_description: "Pubblica la Nota. Funziona anche con \"Ctrl + Invio\", oppure \"Cmd + Invio\"." _placeholders: a: "Come va?" b: "Hai qualcosa da raccontare? Inizia pure..." @@ -2557,6 +2786,8 @@ _notification: youReceivedFollowRequest: "Hai ricevuto una richiesta di follow" yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata" pollEnded: "Risultati del sondaggio." + scheduledNotePosted: "Pubblicazione Nota pianificata" + scheduledNotePostFailed: "Impossibile pubblicare la Nota pianificata" newNote: "Nuove Note" unreadAntennaNote: "Antenna {name}" roleAssigned: "Ruolo assegnato" @@ -2577,7 +2808,7 @@ _notification: createToken: "È stato creato un token di accesso" createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})." _types: - all: "Tutto" + all: "Tutte" note: "Nuove Note" follow: "Follower" mention: "Menzioni" @@ -2585,7 +2816,9 @@ _notification: renote: "Rinota" quote: "Cita" reaction: "Reazioni" - pollEnded: "Sondaggio chiuso." + pollEnded: "Sondaggio terminato" + scheduledNotePosted: "Nota pianificata correttamente" + scheduledNotePostFailed: "La pianificazione della Nota è fallita" receiveFollowRequest: "Richieste di follow in arrivo" followRequestAccepted: "Richieste di follow accettate" roleAssigned: "Ruolo concesso" @@ -2593,7 +2826,7 @@ _notification: achievementEarned: "Risultato raggiunto" exportCompleted: "Esportazione completata" login: "Accessi" - createToken: "Creare un token di accesso" + createToken: "Aggiunto un token di accesso" test: "Notifiche di test" app: "Notifiche da applicazioni" _actions: @@ -2601,7 +2834,7 @@ _notification: reply: "Rispondi" renote: "Rinota" _deck: - alwaysShowMainColumn: "Mostra sempre la colonna principale" + alwaysShowMainColumn: "Mostrare sempre la colonna Principale" columnAlign: "Allineamento delle colonne" columnGap: "Spessore del margine tra colonne" deckMenuPosition: "Posizione del menu Deck" @@ -2618,13 +2851,21 @@ _deck: profile: "Profilo" newProfile: "Nuovo profilo" deleteProfile: "Cancellare il profilo." - introduction: "Combinate le colonne per creare la vostra interfaccia!" - introduction2: "È possibile aggiungere colonne in qualsiasi momento premendo + sulla destra dello schermo." + introduction: "Crea la tua interfaccia combinando le colonne!" + introduction2: "Per aggiungere una colonna, cliccare il bottone + (più) visibile al margine dello schermo." widgetsIntroduction: "Dal menu della colonna, selezionare \"Modifica i riquadri\" per aggiungere un un riquadro con funzionalità" useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice" usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima" flexible: "Larghezza flessibile" enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi" + showHowToUse: "Guarda la spiegazione dell'interfaccia grafica" + _howToUse: + addColumn_title: "Aggiungere colonne" + addColumn_description: "Puoi selezionare un tipo di colonna e aggiungerlo." + settings_title: "Configurazione interfaccia grafica" + settings_description: "Puoi personalizzare i dettagli dell'interfaccia grafica." + switchProfile_title: "Selettore profilo" + switchProfile_description: "Puoi salvare la disposizione dell'interfaccia grafica nel tuo profilo, affinché cambi con comodità." _columns: main: "Principale" widgets: "Riquadri" @@ -2636,7 +2877,7 @@ _deck: mentions: "Menzioni" direct: "Note Dirette" roleTimeline: "Timeline Ruolo" - chat: "Chat" + chat: "Chatta con questa persona" _dialog: charactersExceeded: "Hai superato il limite di {max} caratteri! ({current})" charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({current})" @@ -2685,56 +2926,58 @@ _abuseReport: notifiedWebhook: "Webhook da usare" deleteConfirm: "Vuoi davvero rimuovere il destinatario della notifica?" _moderationLogTypes: - createRole: "Ruolo creato" - deleteRole: "Ruolo eliminato" - updateRole: "Ruolo aggiornato" - assignRole: "Ruolo assegnato" - unassignRole: "Ruolo disassegnato" - suspend: "Sospensione" - unsuspend: "Sospensione rimossa" - addCustomEmoji: "Emoji personalizzata aggiunta" - updateCustomEmoji: "Emoji personalizzata aggiornata" - deleteCustomEmoji: "Emoji personalizzata eliminata" - updateServerSettings: "Impostazioni del server aggiornate" - updateUserNote: "Promemoria di moderazione aggiornato" - deleteDriveFile: "File da Drive eliminato" - deleteNote: "Nota eliminata" - createGlobalAnnouncement: "Annuncio globale creato" - createUserAnnouncement: "Annuncio ai profili iscritti creato" - updateGlobalAnnouncement: "Annuncio globale aggiornato" - updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato" - deleteGlobalAnnouncement: "Annuncio globale eliminato" - deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato" - resetPassword: "Password azzerata" - suspendRemoteInstance: "Istanza remota sospesa" - unsuspendRemoteInstance: "Istanza remota riattivata" - updateRemoteInstanceNote: "Aggiornamento del promemoria di moderazione per il server remoto" - markSensitiveDriveFile: "File nel Drive segnato come esplicito" - unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito" - resolveAbuseReport: "Segnalazione risolta" - forwardAbuseReport: "Segnalazione inoltrata" - updateAbuseReportNote: "Ha aggiornato la segnalazione" - createInvitation: "Genera codice di invito" - createAd: "Banner creato" - deleteAd: "Banner eliminato" - updateAd: "Banner aggiornato" - createAvatarDecoration: "Creazione decorazione della foto profilo" - updateAvatarDecoration: "Aggiornamento decorazione foto profilo" - deleteAvatarDecoration: "Eliminazione decorazione della foto profilo" - unsetUserAvatar: "Rimossa foto profilo" - unsetUserBanner: "Rimossa intestazione profilo" - createSystemWebhook: "Crea un SystemWebhook" - updateSystemWebhook: "Modifica SystemWebhook" - deleteSystemWebhook: "Elimina SystemWebhook" + clearQueue: "Ha cancellato la coda di Attività" + promoteQueue: "Ripeti le attività in coda" + createRole: "Crea un Ruolo" + deleteRole: "Elimina un Ruolo" + updateRole: "Modifica un ruolo" + assignRole: "Assegna un Ruolo" + unassignRole: "Toglie un Ruolo al Profilo" + suspend: "Sospende" + unsuspend: "Solleva la sospensione" + addCustomEmoji: "Aggiunge Emoji personalizzata" + updateCustomEmoji: "Modifica Emoji personalizzata" + deleteCustomEmoji: "Elimina Emoji personalizzata" + updateServerSettings: "Modifica le impostazioni del server" + updateUserNote: "Modifica un promemoria di moderazione" + deleteDriveFile: "Elimina un file dal Drive" + deleteNote: "Elimina una Nota" + createGlobalAnnouncement: "Crea un annuncio globale" + createUserAnnouncement: "Crea un annuncio ai profili già iscritti" + updateGlobalAnnouncement: "Modifica un annuncio globale" + updateUserAnnouncement: "Modifica un annuncio ai profili già iscritti" + deleteGlobalAnnouncement: "Elimina un annuncio globale" + deleteUserAnnouncement: "Elimina un annuncio ai profili già iscritti" + resetPassword: "Azzera la password" + suspendRemoteInstance: "Sospende una istanza remota" + unsuspendRemoteInstance: "Riattiva una istanza remota" + updateRemoteInstanceNote: "Modifica il promemoria di moderazione per il server remoto" + markSensitiveDriveFile: "Aggiunge NSFW a un file nel Drive" + unmarkSensitiveDriveFile: "Toglie NSFW da un file nel Drive" + resolveAbuseReport: "Risolve una segnalazione" + forwardAbuseReport: "Inoltra una segnalazione" + updateAbuseReportNote: "Modifica una segnalazione" + createInvitation: "Genera un codice di invito" + createAd: "Aggiunge un Banner" + deleteAd: "Elimina un Banner" + updateAd: "Modifica un Banner" + createAvatarDecoration: "Crea una decorazione della foto profilo" + updateAvatarDecoration: "Modifica una decorazione della foto profilo" + deleteAvatarDecoration: "Elimina una decorazione della foto profilo" + unsetUserAvatar: "Toglie una foto profilo" + unsetUserBanner: "Toglie una immagine di intestazione profilo" + createSystemWebhook: "Aggiunge un System Webhook" + updateSystemWebhook: "Modifica un System Webhook" + deleteSystemWebhook: "Elimina un System Webhook" createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" - updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" - deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni" - deleteAccount: "Quando viene eliminato un profilo" - deletePage: "Pagina eliminata" - deleteFlash: "Play eliminato" - deleteGalleryPost: "Eliminazione pubblicazione nella Galleria" - deleteChatRoom: "Elimina chat" - updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy" + updateAbuseReportNotificationRecipient: "Modifica un destinatario per le notifiche di segnalazioni" + deleteAbuseReportNotificationRecipient: "Elimina un destinatario per le notifiche di segnalazioni" + deleteAccount: "Elimina un profilo" + deletePage: "Elimina una Pagina" + deleteFlash: "Elimina un Play" + deleteGalleryPost: "Elimina pubblicazione nella Galleria" + deleteChatRoom: "Elimina una Chat" + updateProxyAccountDescription: "Aggiorna la descrizione del profilo proxy" _fileViewer: title: "Dettagli del file" type: "Tipo di file" @@ -2742,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "Caricato il" attachedNotes: "Note a cui è allegato" + usage: "In uso" thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file." _externalResourceInstaller: title: "Installa da sito esterno" @@ -2789,9 +3033,12 @@ _dataSaver: _avatar: title: "Immagine del profilo" description: "Impedire l'animazione per l'immagine del profilo. Le immagini animate possono avere dimensioni file maggiori rispetto a quelle normali, puoi ridurre ulteriormente l'utilizzo dei dati." - _urlPreview: - title: "Anteprime delle URL" - description: "Impedire il caricamento delle anteprime URL." + _urlPreviewThumbnail: + title: "Nascondi le miniature nell'anteprima URL" + description: "Le immagini in miniatura nell'anteprima URL non verranno più caricate." + _disableUrlPreview: + title: "Disabilita l'anteprima URL" + description: "Disabilita la funzione di anteprima URL. A differenza di una semplice immagine in miniatura, questo riduce il tempo necessario per caricare le informazioni collegate." _code: title: "Codice evidenziato" description: "Impedire che il codice sorgente sia automaticamente evidenziato. Evidenziare il codice richiede il caricamento di un file per ogni linguaggio. Puoi evidenziare soltanto il codice che intendi leggere e ridurre il traffico inutilizzato." @@ -2849,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "Impostazioni per l'anteprima delle URL" enable: "Attiva l'anteprima delle URL" + allowRedirect: "Segui i reindirizzamenti per visualizzare le anteprime" + allowRedirectDescription: "Se la URL inserita contiene un reindirizzamento, decidi di seguire il reindirizzamento fino alla destinazione, visualizzandone l'anteprima. Disabilitando questa opzione si risparmiano risorse del server, ma il contenuto effettivo dal reindirizzamento, non verrà visualizzato." timeout: "Timeout dell'anteprima in millisecondi" timeoutDescription: "Impegna al massimo il tempo indicato, altrimenti ignora l'anteprima" maximumContentLength: "Grandezza del contenuto (Content-Length in byte)" @@ -2868,7 +3117,7 @@ _contextMenu: title: "Menu contestuale" app: "Applicazione" appWithShift: "Applicazione Shift+Tasto" - native: "Interfaccia utente del browser" + native: "Interfaccia grafica del browser" _gridComponent: _error: requiredValue: "Campo obbligatorio" @@ -2922,15 +3171,11 @@ _customEmojisManager: uploadSettingDescription: "Questa schermata ti permette di scegliere il comportamento durante il caricamento delle emoji." directoryToCategoryLabel: "Inseriscile in una cartella omonima alla categoria" directoryToCategoryCaption: "Crea il campo categoria in base alla cartella." - emojiInputAreaCaption: "Seleziona l'emoji da registrare utilizzando uno dei metodi." - emojiInputAreaList1: "Trascinare una immagine o una cartella in quest'area" - emojiInputAreaList2: "Clicca per scegliere file dal tuo dispositivo" - emojiInputAreaList3: "Clicca per selezionare dal Drive" confirmRegisterEmojisDescription: "Registrazione delle emoji elencate come nuove emoji personalizzate. Vuoi davvero procedere? (Per evitare sovraccarichi, puoi registrare al massimo {count} emoji per volta)" confirmClearEmojisDescription: "Annullare le modifiche e cancella le emoji nell'elenco. Confermi?" confirmUploadEmojisDescription: "Caricamento sul Drive di {count} file locali. Vuoi davvero procedere?" _embedCodeGen: - title: "Personalizza il codice di incorporamento" + title: "Personalizza il codice per incorporare" header: "Mostra la testata" autoload: "Carica automaticamente di più (sconsigliato)" maxHeight: "Altezza massima" @@ -2939,8 +3184,8 @@ _embedCodeGen: previewIsNotActual: "Poiché supera l'intervallo che può essere visualizzato in anteprima, la visualizzazione vera e propria sarà diversa quando effettivamente incorporata." rounded: "Bordo arrotondato" border: "Aggiungi un bordo al contenitore" - applyToPreview: "Applica all'anteprima" - generateCode: "Crea il codice di incorporamento" + applyToPreview: "Aggiorna l'anteprima" + generateCode: "Crea il codice per incorporare" codeGenerated: "Codice generato" codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web." _selfXssPrevention: @@ -2993,11 +3238,205 @@ _bootErrors: otherOption1: "Nelle impostazioni, cancellare le impostazioni del client e svuotare la cache" otherOption2: "Avviare il client predefinito" otherOption3: "Avviare lo strumento di riparazione" + otherOption4: "Avvia Misskey in modalità sicura" _search: searchScopeAll: "Tutte" searchScopeLocal: "Locale" - searchScopeServer: "Specifiche del server" + searchScopeServer: "Server specifico" searchScopeUser: "Profilo specifico" pleaseEnterServerHost: "Inserire il nome host" pleaseSelectUser: "Per favore, seleziona un profilo" serverHostPlaceholder: "Es: misskey.example.com" +_serverSetupWizard: + installCompleted: "L'installazione di Misskey è completata!" + firstCreateAccount: "Per prima cosa, crea un account amministratore." + accountCreated: "Il tuo account amministratore è stato creato!" + serverSetting: "Configurazione del server" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Questa procedura guidata ti aiuterà a configurare facilmente il tuo server in modo ottimale." + settingsYouMakeHereCanBeChangedLater: "Potrai anche modificare le impostazioni in seguito." + howWillYouUseMisskey: "Come si usa Misskey?" + _use: + single: "Modalità utenza singola" + single_description: "Se intendi usarlo come tuo server personale" + single_youCanCreateMultipleAccounts: "Anche se lo utilizzi come server per una sola persona, puoi creare più account in base alle tue esigenze." + group: "Modalità multi utentza" + group_description: "Invita altre persone fidate ad usare il server insieme a te" + open: "Server aperto" + open_description: "Per ospitare un numero imprecisato di persone" + openServerAdvice: "Ospitare un numero imprecisato di persone comporta dei rischi. Ti consigliamo di adottare un solido sistema di moderazione, in modo da poter gestire eventuali problemi che potrebbero presentarsi pubblicando contenuti proposti da altre persone, che potrebbero essere sconosciute." + openServerAntiSpamAdvice: "Presta molta attenzione alla sicurezza, ad esempio attivando funzionalità anti-bot (iscrizioni automatiche) come reCAPTCHA. Questo può evitare che il tuo server diventi un trampolino di lancio per lo spam di altri." + howManyUsersDoYouExpect: "Quante persone pensi che parteciperanno?" + _scale: + small: "100 persone o meno (piccolo)" + medium: "Da 100 a 1000 persone (medio)" + large: "Oltre 1000 persone (grande)" + largeScaleServerAdvice: "Configurare grandi server potrebbe richiedere conoscenze infrastrutturali avanzate, ad esempio, il bilanciamento del carico e la replicazione del database." + doYouConnectToFediverse: "Vuoi connetterti al Fediverso?" + doYouConnectToFediverse_description1: "Collegandosi a una rete di server distribuiti, denominata Fediverso, potrai scambiare contenuti con altri server, tramite il protocollo di comunicazione ActivityPub." + doYouConnectToFediverse_description2: "Connettersi al Fediverso è anche detto \"federazione\"." + youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi." + remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo" + remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione." + adminInfo: "Informazioni sull'amministratore" + adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste." + adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione." + followingSettingsAreRecommended: "Si consigliano le seguenti impostazioni:" + applyTheseSettings: "Applica questa impostazione" + skipSettings: "Salta l'installazione" + settingsCompleted: "Installazione completata!" + settingsCompleted_description: "Grazie per il tuo impegno. Adesso che hai completato la configurazione, puoi iniziare a utilizzare il tuo server." + settingsCompleted_description2: "Le impostazioni dettagliate del server possono essere effettuate tramite il Pannello di controllo." + donationRequest: "Per favore Fai una donazione" + _donationRequest: + text1: "Misskey è un software libero sviluppato da volontari." + text2: "Se puoi, ti preghiamo di prendere in considerazione l'idea di fare una donazione, così potremo continuare a sviluppare." + text3: "Sono previsti anche dei vantaggi speciali per i sostenitori!" +_uploader: + editImage: "Modifica immagine" + compressedToX: "Compresso in {x}" + savedXPercent: "{x}% risparmiati" + abortConfirm: "Alcuni file non sono stati caricati. Vuoi annullare l'operazione?" + doneConfirm: "Alcuni file non sono stati caricati. Vuoi completarli?" + maxFileSizeIsX: "La dimensione massima del file che puoi caricare è {x}." + allowedTypes: "Tipi di file caricabili" + tip: "Il file non è ancora stato caricato. Puoi controllare, rinominare, comprimere, ritagliare, prima del caricamento. Quando hai finito, premi il bottone \"Carica\" ​​per completare." +_clientPerformanceIssueTip: + title: "Se ritieni che la batteria si stia scaricando troppo" + makeSureDisabledAdBlocker: "Disattiva il tuo AdBlocker" + makeSureDisabledAdBlocker_description: "Gli AdBlocker possono influire sulle prestazioni. Controlla se nel tuo sistema operativo, nel browser o nei componenti aggiuntivi è abilitato un AdBlocker." + makeSureDisabledCustomCss: "Disabilita CSS personalizzato" + makeSureDisabledCustomCss_description: "La riscrittura degli stili CSS può influire sulle prestazioni. Assicurati di non avere CSS personalizzati o estensioni abilitate che sovrascrivano i tuoi stili." + makeSureDisabledAddons: "Disabilitare le estensioni" + makeSureDisabledAddons_description: "Alcune estensioni potrebbero interferire con il funzionamento del client e comprometterne le prestazioni. Prova a disattivare le estensioni del browser e vedi se il problema persiste." +_clip: + tip: "Le clip sono una funzionalità che consente di raggruppare le Note." +_userLists: + tip: "Puoi creare un elenco di Note create da qualsiasi profilo. L'elenco è visualizzato come una sequenza temporale." +watermark: "Filigrana" +defaultPreset: "Impostazioni predefinite" +_watermarkEditor: + tip: "Puoi aggiungere una filigrana, ad esempio con i crediti alle tue immagini." + quitWithoutSaveConfirm: "Uscire senza salvare?" + driveFileTypeWarn: "Formato file non supportato" + driveFileTypeWarnDescription: "Per favore seleziona un file immagine" + title: "Modifica la filigrana" + cover: "Coprire tutto" + repeat: "Disposizione" + preserveBoundingRect: "Fai in modo da non eccedere durante la rotazione" + opacity: "Opacità" + scale: "Dimensioni" + text: "Testo" + qr: "QR Code" + position: "Posizione" + margin: "Margine" + type: "Tipo" + image: "Immagini" + advanced: "Avanzato" + angle: "Angolo" + stripe: "Strisce" + stripeWidth: "Larghezza della linea" + stripeFrequency: "Il numero di linee" + polkadot: "A pallini" + checker: "revisore" + polkadotMainDotOpacity: "Opacità del punto principale" + polkadotMainDotRadius: "Dimensione del punto principale" + polkadotSubDotOpacity: "Opacità del punto secondario" + polkadotSubDotRadius: "Dimensione del punto secondario" + polkadotSubDotDivisions: "Quantità di punti secondari" + leaveBlankToAccountUrl: "Il valore vuoto indica la URL dell'account" + failedToLoadImage: "Impossibile caricare l'immagine" +_imageEffector: + title: "Effetto" + addEffect: "Aggiungi effetto" + discardChangesConfirm: "Scarta le modifiche ed esci?" + failedToLoadImage: "Impossibile caricare l'immagine" + _fxs: + chromaticAberration: "Aberrazione cromatica" + glitch: "Glitch" + mirror: "Specchio" + invert: "Inversione colore" + grayscale: "Bianco e nero" + blur: "Sfocatura" + pixelate: "Mosaico" + colorAdjust: "Correzione Colore" + colorClamp: "Compressione del colore" + colorClampAdvanced: "Compressione del colore (avanzata)" + distort: "Distorsione" + threshold: "Soglia" + zoomLines: "Linea di saturazione" + stripe: "Strisce" + polkadot: "A pallini" + checker: "revisore" + blockNoise: "Attenua rumore" + tearing: "Strappa immagine" + fill: "Riempimento" + _fxProps: + angle: "Angolo" + scale: "Dimensioni" + size: "Dimensioni" + radius: "Raggio" + samples: "Quantità di campioni" + offset: "Posizione" + color: "Colore" + opacity: "Opacità" + normalize: "Normalizza" + amount: "Quantità" + lightness: "Chiaro" + contrast: "Contrasto" + hue: "Tinta" + brightness: "Luminosità" + saturation: "Saturazione" + max: "Valore massimo" + min: "Valore minimo" + direction: "Orientamento" + phase: "Fasare" + frequency: "Frequenza" + strength: "Forza" + glitchChannelShift: "Glitch cambio canale" + seed: "Seme" + redComponent: "Rosso composito" + greenComponent: "Verde composito" + blueComponent: "Blu composito" + threshold: "Soglia" + centerX: "Centro orizzontale" + centerY: "Centro verticale" + zoomLinesSmoothing: "Levigatura" + zoomLinesSmoothingDescription: "Non si possono usare insieme la levigatura e la larghezza della linea centrale." + zoomLinesThreshold: "Limite delle linee zoom" + zoomLinesMaskSize: "Ampiezza del diametro" + zoomLinesBlack: "Bande nere" + circle: "Circolare" +drafts: "Bozze" +_drafts: + select: "Selezionare bozza" + cannotCreateDraftAnymore: "Hai superato il numero massimo di bozze ammissibili." + cannotCreateDraft: "Impossibile creare una bozza di questo contenuto." + delete: "Elimina bozza" + deleteAreYouSure: "Vuoi davvero eliminare la bozza?" + noDrafts: "Non c'è nessuna bozza." + replyTo: "Rispondere a {user}" + quoteOf: "Citare la nota di {user}" + postTo: "Inserire in {channel}" + saveToDraft: "Salva come bozza" + restoreFromDraft: "Recuperare dalle bozze" + restore: "Ripristina" + listDrafts: "Elenco bozze" + schedule: "Pianifica pubblicazione" + listScheduledNotes: "Elenca Note pianificate" + cancelSchedule: "Annulla pianificazione" +qr: "QR Code" +_qr: + showTabTitle: "Visualizza" + readTabTitle: "Leggere" + shareTitle: "{name} {acct}" + shareText: "Seguimi nel Fediverso!" + chooseCamera: "Seleziona fotocamera" + cannotToggleFlash: "Flash non controllabile" + turnOnFlash: "Accendi il flash" + turnOffFlash: "Spegni il flash" + startQr: "Inizia lettura QR Code" + stopQr: "Interrompi lettura QR Code" + noQrCodeFound: "Non trovo alcun QR Code" + scanFile: "Scansiona immagine nel dispositivo" + raw: "Testo" + mfm: "MFM" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b1845603cf..4708a10856 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -74,8 +74,8 @@ youGotNewFollower: "フォローされました" receiveFollowRequest: "フォローリクエストされました" followRequestAccepted: "フォローが承認されました" mention: "メンション" -mentions: "あなた宛て" -directNotes: "ダイレクト投稿" +mentions: "メンション" +directNotes: "指名" importAndExport: "インポートとエクスポート" import: "インポート" export: "エクスポート" @@ -83,6 +83,8 @@ files: "ファイル" download: "ダウンロード" driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。" unfollowConfirm: "{name}のフォローを解除しますか?" +cancelFollowRequestConfirm: "{name}へのフォロー申請をキャンセルしますか?" +rejectFollowRequestConfirm: "{name}からのフォロー申請を拒否しますか?" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" lists: "リスト" @@ -172,7 +174,7 @@ emojiUrl: "絵文字画像URL" addEmoji: "絵文字を追加" settingGuide: "おすすめ設定" cacheRemoteFiles: "リモートのファイルをキャッシュする" -cacheRemoteFilesDescription: "この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。" +cacheRemoteFilesDescription: "この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持します。" youCanCleanRemoteFilesCache: "ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。" cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする" cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。" @@ -182,7 +184,7 @@ flagAsCat: "にゃああああああああああああああ!!!!!! flagAsCatDescription: "にゃにゃにゃ??" flagShowTimelineReplies: "タイムラインにノートへの返信を表示する" flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。" -autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認" +autoAcceptFollowed: "フォロー中ユーザーからのフォロー申請を自動承認" addAccount: "アカウントを追加" reloadAccountsList: "アカウントリストの情報を更新" loginFailed: "ログインに失敗しました" @@ -220,6 +222,7 @@ silenceThisInstance: "サーバーをサイレンス" mediaSilenceThisInstance: "サーバーをメディアサイレンス" operations: "操作" software: "ソフトウェア" +softwareName: "ソフトウェア名" version: "バージョン" metadata: "メタデータ" withNFiles: "{n}つのファイル" @@ -250,9 +253,9 @@ noUsers: "ユーザーはいません" editProfile: "プロフィールを編集" noteDeleteConfirm: "このノートを削除しますか?" pinLimitExceeded: "これ以上ピン留めできません" -intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。" done: "完了" processing: "処理中" +preprocessing: "準備中" preview: "プレビュー" default: "デフォルト" defaultValueIs: "デフォルト: {value}" @@ -298,10 +301,12 @@ uploadFromUrl: "URLアップロード" uploadFromUrlDescription: "アップロードしたいファイルのURL" uploadFromUrlRequested: "アップロードをリクエストしました" uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。" +uploadNFiles: "{n}個のファイルをアップロード" explore: "みつける" messageRead: "既読" +readAllChatMessages: "すべてのメッセージを既読にする" noMoreHistory: "これより過去の履歴はありません" -startChat: "チャットを始める" +startChat: "メッセージを送る" nUsersRead: "{n}人が読みました" agreeTo: "{0}に同意" agree: "同意する" @@ -326,11 +331,13 @@ dark: "ダーク" lightThemes: "明るいテーマ" darkThemes: "暗いテーマ" syncDeviceDarkMode: "デバイスのダークモードと同期する" +switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」がオンになっています。同期をオフにして手動でモードを切り替えますか?" drive: "ドライブ" fileName: "ファイル名" selectFile: "ファイルを選択" selectFiles: "ファイルを選択" selectFolder: "フォルダーを選択" +unselectFolder: "フォルダーの選択を解除" selectFolders: "フォルダーを選択" fileNotSelected: "ファイルが選択されていません" renameFile: "ファイル名を変更" @@ -343,6 +350,7 @@ addFile: "ファイルを追加" showFile: "ファイルを表示" emptyDrive: "ドライブは空です" emptyFolder: "フォルダーは空です" +dropHereToUpload: "ここにファイルをドロップしてアップロード" unableToDelete: "削除できません" inputNewFileName: "新しいファイル名を入力してください" inputNewDescription: "新しいキャプションを入力してください" @@ -475,7 +483,7 @@ notFoundDescription: "指定されたURLに該当するページはありませ uploadFolder: "既定アップロード先" markAsReadAllNotifications: "すべての通知を既読にする" markAsReadAllUnreadNotes: "すべての投稿を既読にする" -markAsReadAllTalkMessages: "すべてのチャットを既読にする" +markAsReadAllTalkMessages: "すべてのダイレクトメッセージを既読にする" help: "ヘルプ" inputMessageHere: "ここにメッセージを入力" close: "閉じる" @@ -535,6 +543,7 @@ regenerate: "再生成" fontSize: "フォントサイズ" mediaListWithOneImageAppearance: "画像が1枚のみのメディアリストの高さ" limitTo: "{x}を上限に" +showMediaListByGridInWideArea: "画面幅が広いときはメディアリストを横並びで表示する" noFollowRequests: "フォロー申請はありません" openImageInNewTab: "画像を新しいタブで開く" dashboard: "ダッシュボード" @@ -575,8 +584,10 @@ showFixedPostForm: "タイムライン上部に投稿フォームを表示する showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)" withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする" newNoteRecived: "新しいノートがあります" +newNote: "新しいノート" sounds: "サウンド" sound: "サウンド" +notificationSoundSettings: "通知音の設定" listen: "聴く" none: "なし" showInPage: "ページで表示" @@ -633,8 +644,8 @@ addRelay: "リレーの追加" inboxUrl: "inboxのURL" addedRelays: "追加済みのリレー" serviceworkerInfo: "プッシュ通知を行うには有効にする必要があります。" -deletedNote: "削除された投稿" -invisibleNote: "非公開の投稿" +deletedNote: "削除されたノート" +invisibleNote: "非公開のノート" enableInfiniteScroll: "自動でもっと見る" visibility: "公開範囲" poll: "アンケート" @@ -768,6 +779,7 @@ lockedAccountInfo: "フォローを承認制にしても、ノートの公開範 alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする" disableShowingAnimatedImages: "アニメーション画像を再生しない" +disableShowingAnimatedImages_caption: "この設定に関わらずアニメーション画像が再生されないときは、ブラウザ・OSのアクセシビリティ設定や省電力設定等が干渉している場合があります。" highlightSensitiveMedia: "メディアがセンシティブであることを分かりやすく表示" verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。" notSet: "未設定" @@ -784,7 +796,6 @@ thisIsExperimentalFeature: "これは実験的な機能です。仕様が変更 developer: "開発者" makeExplorable: "アカウントを見つけやすくする" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" -showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示" duplicate: "複製" left: "左" center: "中央" @@ -792,6 +803,7 @@ wide: "広い" narrow: "狭い" reloadToApplySetting: "設定はページリロード後に反映されます。" needReloadToApply: "反映には再起動が必要です。" +needToRestartServerToApply: "反映にはサーバーの再起動が必要です。" showTitlebar: "タイトルバーを表示する" clearCache: "キャッシュをクリア" onlineUsersCount: "{n}人がオンライン" @@ -998,6 +1010,7 @@ failedToUpload: "アップロード失敗" cannotUploadBecauseInappropriate: "不適切な内容を含む可能性があると判定されたためアップロードできません。" cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。" cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。" +cannotUploadBecauseUnallowedFileType: "許可されていないファイル種別のためアップロードできません。" beta: "ベータ" enableAutoSensitive: "自動センシティブ判定" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。" @@ -1013,6 +1026,9 @@ pushNotificationAlreadySubscribed: "プッシュ通知は有効です" pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応" sendPushNotificationReadMessage: "通知が既読になったらプッシュ通知を削除する" sendPushNotificationReadMessageCaption: "端末の電池消費量が増加する可能性があります。" +pleaseAllowPushNotification: "ブラウザの通知設定を許可してください" +browserPushNotificationDisabled: "通知の送信権限の取得に失敗しました" +browserPushNotificationDisabledDescription: "{serverName}から通知を送信する権限がありません。ブラウザの設定から通知を許可して再度お試しください。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "元に戻す" @@ -1049,6 +1065,7 @@ permissionDeniedError: "操作が拒否されました" permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。" preset: "プリセット" selectFromPresets: "プリセットから選択" +custom: "カスタム" achievements: "実績" gotInvalidResponseError: "サーバーの応答が無効です" gotInvalidResponseErrorDescription: "サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。" @@ -1087,6 +1104,7 @@ prohibitedWordsDescription2: "スペースで区切るとAND指定になり、 hiddenTags: "非表示ハッシュタグ" hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。" notesSearchNotAvailable: "ノート検索は利用できません。" +usersSearchNotAvailable: "ユーザー検索は利用できません。" license: "ライセンス" unfavoriteConfirm: "お気に入り解除しますか?" myClips: "自分のクリップ" @@ -1161,6 +1179,7 @@ installed: "インストール済み" branding: "ブランディング" enableServerMachineStats: "サーバーのマシン情報を公開する" enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする" +showRoleBadgesOfRemoteUsers: "リモートユーザーに付与したロールバッジを表示する" turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。" createInviteCode: "招待コードを作成" createWithOptions: "オプションを指定して作成" @@ -1237,9 +1256,8 @@ showAvatarDecorations: "アイコンのデコレーションを表示" releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" -disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする" useGroupedNotifications: "通知をグルーピング" -signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" +emailVerificationFailedError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" code: "コード" @@ -1299,7 +1317,7 @@ messageToFollower: "フォロワーへのメッセージ" target: "対象" testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。本番環境で使用しないでください。" prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)" -prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。" +prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。ユーザー名(username)に対しても全て小文字に置き換えて検査します。" yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています" yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています" @@ -1309,6 +1327,8 @@ availableRoles: "利用可能なロール" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。" federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" +draft: "下書き" +draftsAndScheduledNotes: "下書きと予約投稿" confirmOnReact: "リアクションする際に確認する" reactAreYouSure: "\" {emoji} \" をリアクションしますか?" markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?" @@ -1325,9 +1345,10 @@ skip: "スキップ" restore: "復元" syncBetweenDevices: "デバイス間で同期" preferenceSyncConflictTitle: "サーバーに設定値が存在します" -preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?" -preferenceSyncConflictChoiceServer: "サーバーの設定値" -preferenceSyncConflictChoiceDevice: "デバイスの設定値" +preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どうしますか?" +preferenceSyncConflictChoiceMerge: "統合する" +preferenceSyncConflictChoiceServer: "サーバーの設定値で上書き" +preferenceSyncConflictChoiceDevice: "デバイスの設定値で上書き" preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル" paste: "ペースト" emojiPalette: "絵文字パレット" @@ -1335,6 +1356,8 @@ postForm: "投稿フォーム" textCount: "文字数" information: "情報" chat: "チャット" +directMessage: "ダイレクトメッセージ" +directMessage_short: "メッセージ" migrateOldSettings: "旧設定情報を移行" migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。" compress: "圧縮" @@ -1346,47 +1369,145 @@ settingsMigrating: "設定を移行しています。しばらくお待ちくだ readonly: "読み取り専用" goToDeck: "デッキへ戻る" federationJobs: "連合ジョブ" +driveAboutTip: "ドライブでは、過去にアップロードしたファイルの一覧が表示されます。
\nノートに添付する際に再利用したり、あとで投稿するファイルを予めアップロードしておくこともできます。
\nファイルを削除すると、今までそのファイルを使用した全ての場所(ノート、ページ、アバター、バナー等)からも見えなくなるので注意してください。
\nフォルダを作って整理することもできます。" +scrollToClose: "スクロールして閉じる" +advice: "アドバイス" +realtimeMode: "リアルタイムモード" +turnItOn: "オンにする" +turnItOff: "オフにする" +emojiMute: "絵文字ミュート" +emojiUnmute: "絵文字ミュート解除" +muteX: "{x}をミュート" +unmuteX: "{x}のミュートを解除" +abort: "中止" +tip: "ヒントとコツ" +redisplayAllTips: "全ての「ヒントとコツ」を再表示" +hideAllTips: "全ての「ヒントとコツ」を非表示" +defaultImageCompressionLevel: "デフォルトの画像圧縮度" +defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。" +defaultCompressionLevel: "デフォルトの圧縮度" +defaultCompressionLevel_description: "低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。" +inMinutes: "分" +inDays: "日" +safeModeEnabled: "セーフモードが有効です" +pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プラグインはすべて無効化されています。" +customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。" +themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。" +thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!" +createUserSpecifiedNote: "ユーザー指定ノートを作成" +schedulePost: "投稿を予約" +scheduleToPostOnX: "{x}に投稿を予約します" +scheduledToPostOnX: "{x}に投稿が予約されています" +schedule: "予約" +scheduled: "予約" +widgets: "ウィジェット" +deviceInfo: "デバイス情報" +deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。" +youAreAdmin: "あなたは管理者です" +frame: "フレーム" +presets: "プリセット" +zeroPadding: "ゼロ埋め" +nothingToConfigure: "設定項目はありません" + +_imageEditing: + _vars: + caption: "ファイルのキャプション" + filename: "ファイル名" + filename_without_ext: "拡張子無しファイル名" + year: "撮影年" + month: "撮影月" + day: "撮影日" + hour: "撮影した時刻(時)" + minute: "撮影した時刻(分)" + second: "撮影した時刻(秒)" + camera_model: "カメラ名" + camera_lens_model: "レンズ名" + camera_mm: "焦点距離" + camera_mm_35: "焦点距離(35mm判換算)" + camera_f: "絞り" + camera_s: "シャッタースピード" + camera_iso: "ISO感度" + gps_lat: "緯度" + gps_long: "経度" + +_imageFrameEditor: + title: "フレームの編集" + tip: "画像にフレームやメタデータを含んだラベルを追加して装飾できます。" + header: "ヘッダー" + footer: "フッター" + borderThickness: "フチの幅" + labelThickness: "ラベルの幅" + labelScale: "ラベルのスケール" + centered: "中央揃え" + captionMain: "キャプション(大)" + captionSub: "キャプション(小)" + availableVariables: "利用可能な変数" + withQrCode: "二次元コード" + backgroundColor: "背景色" + textColor: "文字色" + font: "フォント" + fontSerif: "セリフ" + fontSansSerif: "サンセリフ" + quitWithoutSaveConfirm: "保存せずに終了しますか?" + failedToLoadImage: "画像の読み込みに失敗しました" + +_compression: + _quality: + high: "高品質" + medium: "中品質" + low: "低品質" + _size: + large: "サイズ大" + medium: "サイズ中" + small: "サイズ小" + +_order: + newest: "新しい順" + oldest: "古い順" _chat: + messages: "メッセージ" noMessagesYet: "まだメッセージはありません" newMessage: "新しいメッセージ" - individualChat: "個人チャット" - individualChat_description: "特定ユーザーとの一対一のチャットができます。" - roomChat: "ルームチャット" - roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。" - createRoom: "ルームを作成" - inviteUserToChat: "ユーザーを招待してチャットを始めましょう" - yourRooms: "作成したルーム" - joiningRooms: "参加中のルーム" + individualChat: "個別" + individualChat_description: "特定ユーザーと個別にメッセージのやりとりができます。" + roomChat: "グループ" + roomChat_description: "複数人でメッセージのやりとりができます。\nまた、個別のメッセージを許可していないユーザーとでも、相手が受け入れればやりとりできます。" + createRoom: "グループを作成" + inviteUserToChat: "ユーザーを招待してメッセージを送信しましょう" + yourRooms: "作成したグループ" + joiningRooms: "参加中のグループ" invitations: "招待" noInvitations: "招待はありません" history: "履歴" noHistory: "履歴はありません" - noRooms: "ルームはありません" + noRooms: "グループはありません" inviteUser: "ユーザーを招待" sentInvitations: "送信した招待" join: "参加" ignore: "無視" - leave: "ルームから退出" + leave: "グループから退出" members: "メンバー" searchMessages: "メッセージを検索" home: "ホーム" send: "送信" newline: "改行" - muteThisRoom: "このルームをミュート" - deleteRoom: "ルームを削除" - chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。" - chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。" - chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。" - cannotChatWithTheUser: "このユーザーとのチャットを開始できません" - cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" - chatWithThisUser: "チャットする" - thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。" - thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。" - chatAllowedUsers: "チャットを許可する相手" - chatAllowedUsers_note: "自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。" + muteThisRoom: "このグループをミュート" + deleteRoom: "グループを削除" + chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは有効化されていません。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは読み取り専用となっています。新たに書き込んだり、グループを作成・参加したりすることはできません。" + chatNotAvailableInOtherAccount: "相手のアカウントでダイレクトメッセージが使えない状態になっています。" + cannotChatWithTheUser: "このユーザーとのダイレクトメッセージを開始できません" + cannotChatWithTheUser_description: "ダイレクトメッセージが使えない状態になっているか、相手がダイレクトメッセージを開放していません。" + youAreNotAMemberOfThisRoomButInvited: "あなたはこのグループの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。" + doYouAcceptInvitation: "招待を承認しますか?" + chatWithThisUser: "ダイレクトメッセージ" + thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみメッセージを受け付けています。" + thisUserNotAllowedChatAnyone: "このユーザーは誰からもメッセージを受け付けていません。" + chatAllowedUsers: "メッセージを許可する相手" + chatAllowedUsers_note: "自分からメッセージを送った相手とはこの設定に関わらずメッセージの送受信が可能です。" _chatAllowedUsers: everyone: "誰でも" followers: "自分のフォロワーのみ" @@ -1421,10 +1542,26 @@ _settings: makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする" makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。" useStickyIcons: "アイコンをスクロールに追従させる" + enableHighQualityImagePlaceholders: "高品質な画像のプレースホルダを表示" + uiAnimations: "UIのアニメーション" showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示" ifOn: "オンのとき" ifOff: "オフのとき" enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期" + enablePullToRefresh: "ひっぱって更新" + enablePullToRefresh_description: "マウスでは、ホイールを押し込みながらドラッグします。" + realtimeMode_description: "サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。" + contentsUpdateFrequency: "コンテンツの取得頻度" + contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。" + contentsUpdateFrequency_description2: "リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。" + showUrlPreview: "URLプレビューを表示する" + showAvailableReactionsFirstInNote: "利用できるリアクションを先頭に表示" + showPageTabBarBottom: "ページのタブバーを下部に表示" + emojiPaletteBanner: "絵文字ピッカーに固定表示するプリセットをパレットとして登録したり、ピッカーの表示方法をカスタマイズしたりできます。" + enableAnimatedImages: "アニメーション画像を有効にする" + settingsPersistence_title: "設定の永続化" + settingsPersistence_description1: "設定の永続化を有効にすると、設定情報が失われるのを防止できます。" + settingsPersistence_description2: "環境によっては有効化できない場合があります。" _chat: showSenderName: "送信者の名前を表示" @@ -1434,6 +1571,9 @@ _preferencesProfile: profileName: "プロファイル名" profileNameDescription: "このデバイスを識別する名前を設定してください。" profileNameDescription2: "例: 「メインPC」、「スマホ」など" + manageProfiles: "プロファイルの管理" + shareSameProfileBetweenDevicesIsNotRecommended: "複数のデバイスで同一のプロファイルを共有することは推奨しません。" + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "複数のデバイスで同期したい設定項目が存在する場合は、個別に「複数のデバイスで同期」オプションを有効にしてください。" _preferencesBackup: autoBackup: "自動バックアップ" @@ -1444,6 +1584,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。" autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。" backupFound: "設定のバックアップが見つかりました" + forceBackup: "設定の強制バックアップ" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" @@ -1476,6 +1617,7 @@ _delivery: manuallySuspended: "手動停止中" goneSuspended: "サーバー削除のため停止中" autoSuspendedForNotResponding: "サーバー応答なしのため停止中" + softwareSuspended: "配信停止中のソフトウェアであるため停止中" _bubbleGame: howToPlay: "遊び方" @@ -1563,9 +1705,9 @@ _initialTutorial: public: "すべてのユーザーに公開。" home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。" followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。" - direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。" + direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。" doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。" - doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。" + doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。" localOnly: "他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。" _cw: title: "内容を隠す(CW)" @@ -1609,11 +1751,38 @@ _serverSettings: fanoutTimelineDbFallback: "データベースへのフォールバック" fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" + remoteNotesCleaning: "リモート投稿の自動クリーニング" + remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" + remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。" openRegistration: "アカウントの作成をオープンにする" openRegistrationWarning: "登録を開放することはリスクが伴います。サーバーを常に監視し、トラブルが発生した際にすぐに対応できる体制がある場合のみオンにすることを推奨します。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。" + deliverSuspendedSoftware: "配信停止中のソフトウェア" + deliverSuspendedSoftwareDescription: "脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。" + singleUserMode: "お一人様モード" + singleUserMode_description: "このサーバーを利用するのが自分だけの場合、このモードを有効にすることで動作が最適化されます。" + signToActivityPubGet: "GETリクエストに署名する" + signToActivityPubGet_description: "通常は有効にしてください。連合の通信に関する問題がある場合に、無効にすると改善することがありますが、逆にサーバーによっては通信が不可になることがあります。" + proxyRemoteFiles: "リモートファイルをプロキシする" + proxyRemoteFiles_description: "有効にすると、リモートのファイルをプロキシして提供します。画像のサムネイル生成やユーザーのプライバシー保護に役立ちます。" + allowExternalApRedirect: "ActivityPub経由の照会にリダイレクトを許可する" + allowExternalApRedirect_description: "有効にすると、他のサーバーがこのサーバーを通して第三者のコンテンツを照会することが可能になりますが、コンテンツのなりすましが発生する可能性があります。" + userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲" + userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。" + userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。" + restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直しますか?" + restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。" + entrancePageStyle: "エントランスページのスタイル" + showTimelineForVisitor: "タイムラインを表示する" + showActivitiesForVisitor: "アクティビティを表示する" + + _userGeneratedContentsVisibilityForVisitor: + all: "全て公開" + localOnly: "ローカルコンテンツのみ公開し、リモートコンテンツは非公開" + none: "全て非公開" _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" @@ -1934,6 +2103,8 @@ _role: canManageCustomEmojis: "カスタム絵文字の管理" canManageAvatarDecorations: "アバターデコレーションの管理" driveCapacity: "ドライブ容量" + maxFileSize: "アップロード可能な最大ファイルサイズ" + maxFileSize_caption: "リバースプロキシやCDNなど、前段で別の設定値が存在する場合があります。" alwaysMarkNsfw: "ファイルにNSFWを常に付与" canUpdateBioMedia: "アイコンとバナーの更新を許可" pinMax: "ノートのピン留めの最大数" @@ -1948,6 +2119,7 @@ _role: descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" canHideAds: "広告の非表示" canSearchNotes: "ノート検索の利用" + canSearchUsers: "ユーザー検索の利用" canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" canImportAntennas: "アンテナのインポートを許可" @@ -1955,7 +2127,13 @@ _role: canImportFollowing: "フォローのインポートを許可" canImportMuting: "ミュートのインポートを許可" canImportUserLists: "リストのインポートを許可" - chatAvailability: "チャットを許可" + chatAvailability: "ダイレクトメッセージを許可" + uploadableFileTypes: "アップロード可能なファイル種別" + uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)" + uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。" + noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数" + scheduledNoteLimit: "予約投稿の同時作成可能数" + watermarkAvailable: "ウォーターマーク機能の使用可否" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -2136,6 +2314,7 @@ _theme: install: "テーマのインストール" manage: "テーマの管理" code: "テーマコード" + copyThemeCode: "テーマコードをコピー" description: "説明" installed: "{name}をインストールしました" installedThemes: "インストールされたテーマ" @@ -2195,9 +2374,8 @@ _theme: buttonBg: "ボタンの背景" buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" - driveFolderBg: "ドライブフォルダーの背景" badge: "バッジ" - messageBg: "チャットの背景" + messageBg: "メッセージの背景" fgHighlighted: "強調された文字" _sfx: @@ -2205,7 +2383,7 @@ _sfx: noteMy: "ノート(自分)" notification: "通知" reaction: "リアクション選択時" - chatMessage: "チャットのメッセージ" + chatMessage: "ダイレクトメッセージ" _soundSettings: driveFile: "ドライブの音声を使用" @@ -2242,6 +2420,7 @@ _time: minute: "分" hour: "時間" day: "日" + month: "ヶ月" _2fa: alreadyRegistered: "既に設定は完了しています。" @@ -2284,8 +2463,8 @@ _permissions: "write:favorites": "お気に入りを操作する" "read:following": "フォローの情報を見る" "write:following": "フォロー・フォロー解除する" - "read:messaging": "チャットを見る" - "write:messaging": "チャットを操作する" + "read:messaging": "ダイレクトメッセージを見る" + "write:messaging": "ダイレクトメッセージを操作する" "read:mutes": "ミュートを見る" "write:mutes": "ミュートを操作する" "write:notes": "ノートを作成・削除する" @@ -2358,8 +2537,8 @@ _permissions: "read:clip-favorite": "クリップのいいねを見る" "read:federation": "連合に関する情報を取得する" "write:report-abuse": "違反を報告する" - "write:chat": "チャットを操作する" - "read:chat": "チャットを閲覧する" + "write:chat": "ダイレクトメッセージを操作する" + "read:chat": "ダイレクトメッセージを閲覧する" _auth: shareAccessTitle: "アプリへのアクセス許可" @@ -2374,6 +2553,7 @@ _auth: scopeUser: "以下のユーザーとして操作しています" pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。" byClickingYouWillBeRedirectedToThisUrl: "アクセスを許可すると、自動で以下のURLに遷移します" + alreadyAuthorized: "このアプリケーションは既にアクセスが許可されています。" _antennaSources: all: "全てのノート" @@ -2421,8 +2601,47 @@ _widgets: _userList: chooseList: "リストを選択" clicker: "クリッカー" - birthdayFollowings: "今日誕生日のユーザー" - chat: "チャット" + birthdayFollowings: "もうすぐ誕生日のユーザー" + chat: "ダイレクトメッセージ" + +_widgetOptions: + showHeader: "ヘッダーを表示" + transparent: "背景を透明にする" + height: "高さ" + _button: + colored: "色付き" + _clock: + size: "サイズ" + thickness: "針の太さ" + thicknessThin: "細い" + thicknessMedium: "普通" + thicknessThick: "太い" + graduations: "文字盤の目盛り" + graduationDots: "ドット" + graduationArabic: "アラビア数字" + fadeGraduations: "目盛りをフェード" + sAnimation: "秒針のアニメーション" + sAnimationElastic: "リアル" + sAnimationEaseOut: "滑らか" + twentyFour: "24時間表示" + labelTime: "時刻" + labelTz: "タイムゾーン" + labelTimeAndTz: "時刻とタイムゾーン" + timezone: "タイムゾーン" + showMs: "ミリ秒を表示" + showLabel: "ラベルを表示" + _jobQueue: + sound: "音を鳴らす" + _rss: + url: "RSSフィードのURL" + refreshIntervalSec: "更新間隔(秒)" + maxEntries: "最大表示件数" + _rssTicker: + shuffle: "表示順をシャッフル" + duration: "ティッカーのスクロール速度(秒)" + reverse: "逆方向にスクロール" + _birthdayFollowings: + period: "期間" _cw: hide: "隠す" @@ -2460,15 +2679,31 @@ _visibility: homeDescription: "ホームタイムラインのみに公開" followers: "フォロワー" followersDescription: "自分のフォロワーのみに公開" - specified: "ダイレクト" + specified: "指名" specifiedDescription: "指定したユーザーのみに公開" disableFederation: "連合なし" disableFederationDescription: "他サーバーへの配信を行いません" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "アップロードされていないファイルがありますが、破棄してフォームを閉じますか?" + uploaderTip: "ファイルはまだアップロードされていません。ファイルのメニューから、リネームや画像のクロップ、ウォーターマークの付与、圧縮の有無などを設定できます。ファイルはノート投稿時に自動でアップロードされます。" 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: "何かありましたか?" @@ -2505,7 +2740,7 @@ _exportOrImport: userLists: "リスト" excludeMutingUsers: "ミュートしているユーザーを除外" excludeInactiveUsers: "使われていないアカウントを除外" - withReplies: "インポートした人による返信をTLに含むようにする" + withReplies: "返信をTLに含むかの情報がファイルにない場合に、インポートした人による返信をTLに含むようにする" _charts: federation: "連合" @@ -2624,10 +2859,12 @@ _notification: youReceivedFollowRequest: "フォローリクエストが来ました" yourFollowRequestAccepted: "フォローリクエストが承認されました" pollEnded: "アンケートの結果が出ました" + scheduledNotePosted: "予約ノートが投稿されました" + scheduledNotePostFailed: "予約ノートの投稿に失敗しました" newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されました" - chatRoomInvitationReceived: "チャットルームへ招待されました" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待されました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" testNotification: "通知テスト" @@ -2654,10 +2891,12 @@ _notification: quote: "引用" reaction: "リアクション" pollEnded: "アンケートが終了" + scheduledNotePosted: "予約投稿が成功した" + scheduledNotePostFailed: "予約投稿が失敗した" receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" roleAssigned: "ロールが付与された" - chatRoomInvitationReceived: "チャットルームへ招待された" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待された" achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" @@ -2695,6 +2934,15 @@ _deck: usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります" flexible: "幅を自動調整" enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする" + showHowToUse: "UIの説明を見る" + + _howToUse: + addColumn_title: "カラム追加" + addColumn_description: "カラムの種類を選んで追加できます。" + settings_title: "UI設定" + settings_description: "デッキUIの詳細設定を行えます。" + switchProfile_title: "プロファイル切り替え" + switchProfile_description: "UIのレイアウトをプロファイルとして保存し、いつでも切り替えられるようにできます。" _columns: main: "メイン" @@ -2704,10 +2952,10 @@ _deck: antenna: "アンテナ" list: "リスト" channel: "チャンネル" - mentions: "あなた宛て" - direct: "ダイレクト" + mentions: "メンション" + direct: "指名" roleTimeline: "ロールタイムライン" - chat: "チャット" + chat: "ダイレクトメッセージ" _dialog: charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}" @@ -2762,6 +3010,8 @@ _abuseReport: deleteConfirm: "通知先を削除しますか?" _moderationLogTypes: + clearQueue: "ジョブキューをクリア" + promoteQueue: "キューのジョブを再試行" createRole: "ロールを作成" deleteRole: "ロールを削除" updateRole: "ロールを更新" @@ -2810,7 +3060,7 @@ _moderationLogTypes: deletePage: "ページを削除" deleteFlash: "Playを削除" deleteGalleryPost: "ギャラリーの投稿を削除" - deleteChatRoom: "チャットルームを削除" + deleteChatRoom: "ダイレクトメッセージのグループを削除" updateProxyAccountDescription: "プロキシアカウントの説明を更新" _fileViewer: @@ -2820,6 +3070,7 @@ _fileViewer: url: "URL" uploadedAt: "追加日" attachedNotes: "添付されているノート" + usage: "利用" thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。" _externalResourceInstaller: @@ -2869,9 +3120,12 @@ _dataSaver: _avatar: title: "アイコン画像のアニメーションを無効化" description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。" - _urlPreview: + _urlPreviewThumbnail: title: "URLプレビューのサムネイルを非表示" description: "URLプレビューのサムネイル画像が読み込まれなくなります。" + _disableUrlPreview: + title: "URLプレビューを無効化" + description: "URLプレビュー機能を無効化します。サムネイル画像だけと違い、リンク先の情報の読み込み自体を削減できます。" _code: title: "コードハイライトを非表示" description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。" @@ -3006,6 +3260,8 @@ _offlineScreen: _urlPreviewSetting: title: "URLプレビューの設定" enable: "URLプレビューを有効にする" + allowRedirect: "プレビュー先のリダイレクトを許可" + allowRedirectDescription: "入力されたURLがリダイレクトされる場合に、そのリダイレクト先をたどってプレビューを表示するかどうかを設定します。無効にするとサーバーリソースの節約になりますが、リダイレクト先の内容は表示されなくなります。" timeout: "プレビュー取得時のタイムアウト(ms)" timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。" maximumContentLength: "Content-Lengthの最大値(byte)" @@ -3084,10 +3340,6 @@ _customEmojisManager: uploadSettingDescription: "この画面で絵文字アップロードを行う際の動作を設定できます。" directoryToCategoryLabel: "ディレクトリ名を\"category\"に入力する" directoryToCategoryCaption: "ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を\"category\"に入力します。" - emojiInputAreaCaption: "いずれかの方法で登録する絵文字を選択してください。" - emojiInputAreaList1: "この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ" - emojiInputAreaList2: "このリンクをクリックしてPCから選択する" - emojiInputAreaList3: "このリンクをクリックしてドライブから選択する" confirmRegisterEmojisDescription: "リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)" confirmClearEmojisDescription: "編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?" confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?" @@ -3161,6 +3413,7 @@ _bootErrors: otherOption1: "クライアント設定とキャッシュを削除" otherOption2: "簡易クライアントを起動" otherOption3: "修復ツールを起動" + otherOption4: "Misskeyをセーフモードで起動" _search: searchScopeAll: "全て" @@ -3170,3 +3423,207 @@ _search: pleaseEnterServerHost: "サーバーのホストを入力してください" pleaseSelectUser: "ユーザーを選択してください" serverHostPlaceholder: "例: misskey.example.com" + +_serverSetupWizard: + installCompleted: "Misskeyのインストールが完了しました!" + firstCreateAccount: "まずは、管理者アカウントを作成しましょう。" + accountCreated: "管理者アカウントが作成されました!" + serverSetting: "サーバーの設定" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "このウィザードで簡単に最適なサーバーの設定が行えます。" + settingsYouMakeHereCanBeChangedLater: "ここでの設定は、あとからでも変更できます。" + howWillYouUseMisskey: "Misskeyをどのように使いますか?" + _use: + single: "お一人様サーバー" + single_description: "自分専用のサーバーとして、一人で使う" + single_youCanCreateMultipleAccounts: "お一人様サーバーとして運用する場合でも、アカウントは必要に応じて複数作成可能です。" + group: "グループサーバー" + group_description: "信頼できる他の利用者を招待して、複数人で使う" + open: "オープンサーバー" + open_description: "不特定多数の利用者を受け入れる運営を行う" + openServerAdvice: "不特定多数の利用者を受け入れることはリスクが伴います。トラブルに対処できるよう、確実なモデレーション体制で運営することを推奨します。" + openServerAntiSpamAdvice: "自サーバーがスパムの踏み台にならないように、reCAPTCHAといったアンチボット機能を有効にするなど、セキュリティについても細心の注意が必要です。" + howManyUsersDoYouExpect: "どれくらいの人数を想定していますか?" + _scale: + small: "100人以下 (小規模)" + medium: "100人以上1000人以下 (中規模)" + large: "1000人以上 (大規模)" + largeScaleServerAdvice: "大規模なサーバーでは、ロードバランシングやデータベースのレプリケーションなど、高度なインフラストラクチャーの知識が必要になる場合があります。" + doYouConnectToFediverse: "Fediverseと接続しますか?" + doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" + doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" + youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" + remoteContentsCleaning: "リモートコンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" + adminInfo: "管理者情報" + adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" + adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" + followingSettingsAreRecommended: "以下の設定が推奨されます" + applyTheseSettings: "この設定を適用" + skipSettings: "設定をスキップ" + settingsCompleted: "設定が完了しました!" + settingsCompleted_description: "お疲れ様でした。準備が整ったので、さっそくサーバーの使用を開始できます。" + settingsCompleted_description2: "詳細なサーバー設定は、「コントロールパネル」から行えます。" + donationRequest: "寄付のお願い" + _donationRequest: + text1: "Misskeyは有志によって開発されている無料のソフトウェアです。" + text2: "今後も開発を続けられるように、よろしければぜひカンパをお願いいたします。" + text3: "支援者向け特典もあります!" + +_uploader: + editImage: "画像の編集" + compressedToX: "{x}に圧縮" + savedXPercent: "{x}%節約" + abortConfirm: "アップロードされていないファイルがありますが、中止しますか?" + doneConfirm: "アップロードされていないファイルがありますが、完了しますか?" + maxFileSizeIsX: "アップロード可能な最大ファイルサイズは{x}です。" + allowedTypes: "アップロード可能なファイル種別" + tip: "ファイルはまだアップロードされていません。このダイアログで、アップロード前の確認・リネーム・圧縮・クロッピングなどが行えます。準備が出来たら、「アップロード」ボタンを押してアップロードを開始できます。" + +_clientPerformanceIssueTip: + title: "バッテリー消費が多いと感じたら" + makeSureDisabledAdBlocker: "アドブロッカーを無効にしてください" + makeSureDisabledAdBlocker_description: "アドブロッカーはパフォーマンスに影響を及ぼすことがあります。OSの機能やブラウザの機能・アドオンなどでアドブロッカーが有効になっていないか確認してください。" + makeSureDisabledCustomCss: "カスタムCSSを無効にしてください" + makeSureDisabledCustomCss_description: "スタイルを上書きするとパフォーマンスに影響を及ぼすことがあります。カスタムCSSや、スタイルを上書きする拡張機能が有効になっていないか確認してください。" + makeSureDisabledAddons: "拡張機能を無効にしてください" + makeSureDisabledAddons_description: "一部の拡張機能はクライアントの動作に干渉しパフォーマンスに影響を及ぼすことがあります。ブラウザの拡張機能を無効にして改善するか確認してください。" + +_clip: + tip: "クリップは、ノートをまとめることができる機能です。" + +_userLists: + tip: "任意のユーザーが含まれるリストを作成できます。作成したリストはタイムラインとして表示可能です。" + +watermark: "ウォーターマーク" +defaultPreset: "デフォルトのプリセット" +_watermarkEditor: + tip: "画像にクレジット情報などのウォーターマークを追加できます。" + quitWithoutSaveConfirm: "保存せずに終了しますか?" + driveFileTypeWarn: "このファイルは対応していません" + driveFileTypeWarnDescription: "画像ファイルを選択してください" + title: "ウォーターマークの編集" + cover: "全体に被せる" + repeat: "敷き詰める" + preserveBoundingRect: "回転時はみ出ないように調整する" + opacity: "不透明度" + scale: "サイズ" + text: "テキスト" + qr: "二次元コード" + position: "位置" + margin: "マージン" + type: "タイプ" + image: "画像" + advanced: "高度" + angle: "角度" + stripe: "ストライプ" + stripeWidth: "ラインの幅" + stripeFrequency: "ラインの数" + polkadot: "ポルカドット" + checker: "チェッカー" + polkadotMainDotOpacity: "メインドットの不透明度" + polkadotMainDotRadius: "メインドットの大きさ" + polkadotSubDotOpacity: "サブドットの不透明度" + polkadotSubDotRadius: "サブドットの大きさ" + polkadotSubDotDivisions: "サブドットの数" + leaveBlankToAccountUrl: "空欄にするとアカウントのURLになります" + failedToLoadImage: "画像の読み込みに失敗しました" + +_imageEffector: + title: "エフェクト" + addEffect: "エフェクトを追加" + discardChangesConfirm: "変更を破棄して終了しますか?" + failedToLoadImage: "画像の読み込みに失敗しました" + + _fxs: + chromaticAberration: "色収差" + glitch: "グリッチ" + mirror: "ミラー" + invert: "色の反転" + grayscale: "白黒" + blur: "ぼかし" + pixelate: "モザイク" + colorAdjust: "色調補正" + colorClamp: "色の圧縮" + colorClampAdvanced: "色の圧縮(高度)" + distort: "歪み" + threshold: "二値化" + zoomLines: "集中線" + stripe: "ストライプ" + polkadot: "ポルカドット" + checker: "チェッカー" + blockNoise: "ブロックノイズ" + tearing: "ティアリング" + fill: "塗りつぶし" + + _fxProps: + angle: "角度" + scale: "サイズ" + size: "サイズ" + radius: "半径" + samples: "サンプル数" + offset: "位置" + color: "色" + opacity: "不透明度" + normalize: "正規化" + amount: "量" + lightness: "明るさ" + contrast: "コントラスト" + hue: "色相" + brightness: "輝度" + saturation: "彩度" + max: "最大値" + min: "最小値" + direction: "方向" + phase: "位相" + frequency: "頻度" + strength: "強さ" + glitchChannelShift: "ズレ" + seed: "シード値" + redComponent: "赤色成分" + greenComponent: "緑色成分" + blueComponent: "青色成分" + threshold: "しきい値" + centerX: "中心X" + centerY: "中心Y" + zoomLinesSmoothing: "スムージング" + zoomLinesSmoothingDescription: "スムージングと集中線の幅の設定は併用できません。" + zoomLinesThreshold: "集中線の幅" + zoomLinesMaskSize: "中心径" + zoomLinesBlack: "黒色にする" + circle: "円形" + +drafts: "下書き" +_drafts: + select: "下書きを選択" + cannotCreateDraftAnymore: "下書きの作成可能数を超えています。" + cannotCreateDraft: "この内容では下書きを作成できません。" + delete: "下書きを削除" + deleteAreYouSure: "下書きを削除しますか?" + noDrafts: "下書きはありません" + replyTo: "{user}への返信" + quoteOf: "{user}のノートへの引用" + postTo: "{channel}への投稿" + saveToDraft: "下書きへ保存" + restoreFromDraft: "下書きから復元" + restore: "復元" + listDrafts: "下書き一覧" + schedule: "投稿予約" + listScheduledNotes: "予約投稿一覧" + cancelSchedule: "予約解除" + +qr: "二次元コード" +_qr: + showTabTitle: "表示" + readTabTitle: "読み取る" + shareTitle: "{name} {acct}" + shareText: "Fediverseで私をフォローしてください!" + chooseCamera: "カメラを選択" + cannotToggleFlash: "ライト選択不可" + turnOnFlash: "ライトをオンにする" + turnOffFlash: "ライトをオフにする" + startQr: "コードリーダーを再開" + stopQr: "コードリーダーを停止" + noQrCodeFound: "QRコードが見つかりません" + scanFile: "端末の画像をスキャン" + raw: "テキスト" + mfm: "MFM" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 378eaf2ad5..4a2cc3a9b8 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -220,6 +220,7 @@ silenceThisInstance: "サーバーサイレンスすんで?" mediaSilenceThisInstance: "サーバーをメディアサイレンス" operations: "操作" software: "ソフトウェア" +softwareName: "ソフトウェア名" version: "バージョン" metadata: "メタデータ" withNFiles: "{n}個のファイル" @@ -234,7 +235,7 @@ clearQueue: "キューをほかす" clearQueueConfirmTitle: "キューをほかしとこか?" clearQueueConfirmText: "未配達の投稿は配送されんなるで。ふつうこの操作を行う必要は無いんやけどな。" clearCachedFiles: "キャッシュをほかす" -clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?" +clearCachedFilesConfirm: "キャッシュされとるリモートファイルを全部ほかしてええか?" blockedInstances: "ブロックしたサーバー" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできひんくなるで。" silencedInstances: "サーバーサイレンスされてんねん" @@ -250,9 +251,9 @@ noUsers: "ユーザーはおらん" editProfile: "プロフィールをいじる" noteDeleteConfirm: "このノートをほかしてええか?" pinLimitExceeded: "これ以上ピン留めできひん" -intro: "Misskeyのインストールが完了したで!管理者アカウントを作ってや。" done: "でけた" processing: "処理しとる" +preprocessing: "準備中" preview: "プレビュー" default: "デフォルト" defaultValueIs: "デフォルト: {value}" @@ -300,12 +301,14 @@ uploadFromUrlRequested: "アップロードしたい言うといたで" uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。" explore: "みつける" messageRead: "もう読んだ" +readAllChatMessages: "メッセージを全部読んだことにしとく" noMoreHistory: "これより昔のんはあらへんで" +startChat: "チャットを始めよか" nUsersRead: "{n}人が読んでもうた" agreeTo: "{0}に同意したで" agree: "せやな" -agreeBelow: "下記に同意したる" -basicNotesBeforeCreateAccount: "よう読んでやってや" +agreeBelow: "下記に同意するわ" +basicNotesBeforeCreateAccount: "よう読んどいてや" termsOfService: "使うための決め事" start: "始める" home: "ホーム" @@ -325,11 +328,13 @@ dark: "ダーク" lightThemes: "デイゲーム" darkThemes: "ナイトゲーム" syncDeviceDarkMode: "デバイスのダークモードと一緒にする" +switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」がオンになってるで。同期切って手動でモード切り替える?" drive: "ドライブ" fileName: "ファイル名" selectFile: "ファイル選んでや" selectFiles: "ファイル選んでや" selectFolder: "フォルダ選んでや" +unselectFolder: "フォルダーの選択を解除" selectFolders: "フォルダ選んでや" fileNotSelected: "ファイルが選択されてへんで" renameFile: "ファイル名をいらう" @@ -420,13 +425,13 @@ antennaSource: "受信ソース(このソースは食われへん)" antennaKeywords: "受信キーワード" antennaExcludeKeywords: "除外キーワード" antennaExcludeBots: "Botアカウントを除外" -antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や" +antennaKeywordsDescription: "スペースで区切ったらAND指定で、改行で区切ったらOR指定や" notifyAntenna: "新しいノートを通知すんで" withFileAntenna: "なんか添付されたノートだけ" +excludeNotesInSensitiveChannel: "センシティブなチャンネルのノートは入れんとくわ" enableServiceworker: "ブラウザにプッシュ通知が行くようにする" antennaUsersDescription: "ユーザー名を改行で区切ったってな" caseSensitive: "大文字と小文字は別もんや" -withReplies: "返信も入れたって" connectedTo: "次のアカウントに繋がっとるで" notesAndReplies: "投稿と返信" withFiles: "ファイル付いとる" @@ -469,9 +474,9 @@ newPasswordIs: "今度のパスワードは「{password}」や" reduceUiAnimation: "UIの動きやアニメーションを少なする" share: "わけわけ" notFound: "見つからへんね" -notFoundDescription: "言われたURLにはまるページはなかったで。" +notFoundDescription: "言われたURLのページはなかったで。" uploadFolder: "とりあえずアップロードしたやつ置いとく所" -markAsReadAllNotifications: "通知はもう全て読んだわっ" +markAsReadAllNotifications: "通知はもう全部読んだわ" markAsReadAllUnreadNotes: "投稿は全て読んだわっ" markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ" help: "ヘルプ" @@ -552,7 +557,7 @@ showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを objectStorage: "オブジェクトストレージ" useObjectStorage: "オブジェクトストレージを使う" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "参照に使うにURLやで。CDNやProxyを使用してるんならそのURL、S3: 'https://.s3.amazonaws.com'、GCSとかなら: 'https://storage.googleapis.com/'。" +objectStorageBaseUrlDesc: "参照に使うURLやで。CDNやProxyを使用してるんならそのURL、S3: 'https://.s3.amazonaws.com'、GCSとかなら: 'https://storage.googleapis.com/'。" objectStorageBucket: "Bucket" objectStorageBucketDesc: "使ってるサービスのbucket名を選んでな" objectStoragePrefix: "Prefix" @@ -569,17 +574,19 @@ objectStorageSetPublicRead: "アップロードした時に'public-read'を設 s3ForcePathStyleDesc: "s3ForcePathStyleを使たらバケット名をURLのホスト名やなくてパスの一部として必ず指定させるようになるで。セルフホストされたMinioとかを使うてるんやったら有効にせなあかん場合があるで。" serverLogs: "サーバーログ" deleteAll: "全部ほかす" -showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?" +showFixedPostForm: "タイムラインの上の方で投稿できるようにするわ" showFixedPostFormInChannel: "タイムラインの上の方で投稿できるようにするわ(チャンネル)" withRepliesByDefaultForNewlyFollowed: "フォローする時、デフォルトで返信をタイムラインに含むようにしよか" newNoteRecived: "新しいノートがあるで" +newNote: "新しいノートがあるで" sounds: "音" sound: "音" +notificationSoundSettings: "通知音の設定" listen: "聴く" none: "なし" showInPage: "ページで表示" popout: "ポップアウト" -volume: "やかましさ" +volume: "音のでかさ" masterVolume: "全体のやかましさ" notUseSound: "音出さへん" useSoundOnlyWhenActive: "Misskeyがアクティブなときだけ音出す" @@ -595,7 +602,7 @@ nothing: "あらへん" installedDate: "インストールした日時" lastUsedDate: "最後に使った日時" state: "状態" -sort: "仕分ける" +sort: "並び替え" ascendingOrder: "小さい順" descendingOrder: "大きい順" scratchpad: "スクラッチパッド" @@ -655,9 +662,9 @@ useBlurEffectForModal: "モーダルにぼかし効果を使用" useFullReactionPicker: "フルフルのツッコミピッカーを使う" width: "幅" height: "高さ" -large: "大" -medium: "中" -small: "小" +large: "でかい" +medium: "ふつう" +small: "ちいさい" generateAccessToken: "アクセストークンの発行" permission: "権限" adminPermission: "管理者権限" @@ -682,7 +689,7 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する" smtpSecureInfo: "STARTTLS使っとる時はオフにしてや。" testEmail: "配信テスト" wordMute: "ワードミュート" -wordMuteDescription: "指定した語句が入ってるノートを最小化するで。最小化されたノートをクリックしたら、表示できるようになるで。" +wordMuteDescription: "指定した語句が入ってるノートをちっさくするで。ちっさくなったノートをクリックしたら中身を見れるで。" hardWordMute: "ハードワードミュート" showMutedWord: "ミュートされたワードを表示するで" hardWordMuteDescription: "指定した語句が入ってるノートを隠すで。ワードミュートとちゃうて、ノートは完全に表示されんようになるで。" @@ -694,6 +701,7 @@ userSaysSomethingAbout: "{name}が「{word}」についてなんか言うてた makeActive: "使うで" display: "表示" copy: "コピー" +copiedToClipboard: "クリップボードにコピーされたで" metrics: "メトリクス" overview: "概要" logs: "ログ" @@ -715,7 +723,7 @@ behavior: "動作" sample: "サンプル" abuseReports: "通報" reportAbuse: "通報" -reportAbuseRenote: "リノート苦情だすで?" +reportAbuseRenote: "リノートの苦情出す" reportAbuseOf: "{name}を通報する" fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。" abuseReported: "無事内容が送信されたみたいやで。おおきに〜。" @@ -765,6 +773,7 @@ lockedAccountInfo: "フォローを承認制にしとっても、ノートの公 alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" disableShowingAnimatedImages: "アニメーション画像を再生せんとくで" +disableShowingAnimatedImages_caption: "この設定を変えてもアニメーション画像が再生されへん時は、ブラウザとかOSのアクセシビリティ設定とか省電力設定の方が悪さしてるかもしれへんで。" highlightSensitiveMedia: "きわどいことをめっっちゃわかりやすくする" verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。" notSet: "未設定" @@ -781,7 +790,6 @@ thisIsExperimentalFeature: "これは実験的な機能やから、仕様が変 developer: "開発者やで" makeExplorable: "アカウントを見つけやすくするで" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。" -showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示するで" duplicate: "複製" left: "左" center: "真ん中" @@ -789,6 +797,7 @@ wide: "広い" narrow: "狭い" reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?" needReloadToApply: "反映には再起動せなあかんで" +needToRestartServerToApply: "反映にはサーバーを再起動せなあかんのよ。" showTitlebar: "タイトルバーを見せる" clearCache: "キャッシュをほかす" onlineUsersCount: "{n}人が起きとるで" @@ -874,7 +883,7 @@ startingperiod: "始めた期間" memo: "メモ" priority: "優先度" high: "高い" -middle: "中" +middle: "ふつう" low: "低い" emailNotConfiguredWarning: "メアドの設定がされてへんで。" ratio: "比率" @@ -976,6 +985,7 @@ document: "ドキュメント" numberOfPageCache: "ページ、どんだけキャッシュすんの?" numberOfPageCacheDescription: "増やすと使いやすくなるけど、負荷とメモリ使用量が増えてくで。一長一短やな。" logoutConfirm: "ログアウトしまっか?" +logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消されてまうで。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にするとええで。" lastActiveDate: "最後に使った日時" statusbar: "ステータスバー" pleaseSelect: "選んだってやー" @@ -994,6 +1004,7 @@ failedToUpload: "アップロードに失敗してもうたわ…" cannotUploadBecauseInappropriate: "きわどい内容を含むかもしれへんって言われたからアップロードできへんわ。" cannotUploadBecauseNoFreeSpace: "ドライブがもうパンパンやからアップロードできへんわ。" cannotUploadBecauseExceedsFileSizeLimit: "ファイルが思うたよりも大きいさかいアップロードできへんでこれ。" +cannotUploadBecauseUnallowedFileType: "許可されてへんファイル種別やからアップロードできへんっぽい。" beta: "ベータ" enableAutoSensitive: "自動できわどいか判断する" enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。" @@ -1009,6 +1020,9 @@ pushNotificationAlreadySubscribed: "プッシュ通知はオンになってる pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に対応してないみたいやで。" sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を消すで" sendPushNotificationReadMessageCaption: "あんたの端末の電池使う量が増えるかもしれん。" +pleaseAllowPushNotification: "ブラウザの通知設定を許可してな" +browserPushNotificationDisabled: "通知の送信権限が取れんかったわ" +browserPushNotificationDisabledDescription: "今 {serverName} から通知を送るための権限が無いから、ブラウザの設定で通知を許可してもっかい試してな。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "元に戻す" @@ -1045,6 +1059,7 @@ permissionDeniedError: "操作が拒否されてもうた。" permissionDeniedErrorDescription: "このアカウントはこれやったらアカンって。" preset: "プリセット" selectFromPresets: "プリセットから選ぶ" +custom: "カスタム" achievements: "実績" gotInvalidResponseError: "サーバー黙っとるわ、知らんけど" gotInvalidResponseErrorDescription: "サーバーいま日曜日。またきて月曜日。" @@ -1083,6 +1098,7 @@ prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワ hiddenTags: "見えてへんハッシュタグ" hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。" notesSearchNotAvailable: "なんかノート探せへん。" +usersSearchNotAvailable: "ユーザーを探すことはできへんみたいや。" license: "ライセンス" unfavoriteConfirm: "ほんまに気に入らんの?" myClips: "自分のクリップ" @@ -1233,9 +1249,8 @@ showAvatarDecorations: "アイコンのデコレーション映す" releaseToRefresh: "離したらリロード" refreshing: "リロードしとる" pullDownToRefresh: "引っ張ってリロードするで" -disableStreamingTimeline: "タイムラインのリアルタイム更新をやめるで" useGroupedNotifications: "通知をグループ分けして出すで" -signupPendingError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。" +emailVerificationFailedError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。" cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。" doReaction: "ツッコむで" code: "コード" @@ -1307,16 +1322,120 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用 federationDisabled: "このサーバーは連合が無効化されてるで。他のサーバーのユーザーとやり取りすることはできひんで。" confirmOnReact: "ツッコむときに確認とる" reactAreYouSure: "\" {emoji} \" でツッコむ?" +markAsSensitiveConfirm: "このメディアをきわどい扱いしときますか?" +unmarkAsSensitiveConfirm: "このメディアはやっぱきわどくなかったってことでええんか?" +noName: "名前はあらへんで" +preferenceSyncConflictTitle: "サーバーに設定値があるみたいやわ" +preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存するねんけど、この設定項目はサーバーに保存されたやつがあるみたいやわ。どないするん?" +preferenceSyncConflictChoiceMerge: "ガッチャンコしよか" +preferenceSyncConflictChoiceCancel: "同期の有効化はやめとくわ" postForm: "投稿フォーム" information: "情報" +directMessage: "チャットしよか" +migrateOldSettings: "旧設定情報をお引っ越し" +migrateOldSettings_description: "通常これは自動で行われるはずなんやけど、なんかの理由で上手く移行できへんかったときは手動で移行処理をポチっとできるで。今の設定情報は上書きされるで。" +settingsMigrating: "設定を移行しとるで。ちょっと待っとってな... (後で、設定→その他→旧設定情報を移行 で手動で移行することもできるで)" +driveAboutTip: "ドライブでは、今までアップロードしたファイルがずらーっと表示されるで。
\nノートにファイルをもっかいのっけたり、あとで投稿するファイルをその辺に置いとくこともできるねん。
\nファイルをほかすと、前にそのファイルをのっけた全部の場所(ノート、ページ、アバター、バナー等)からも見えんくなるから気いつけてな。
\nフォルダを作って整理することもできるで。" +turnItOn: "オンにしとこ" +turnItOff: "オフでええわ" +emojiUnmute: "絵文字ミュートやめたる" +unmuteX: "{x}のミュートやめたる" +redisplayAllTips: "全部の「ヒントとコツ」をもっかい見して" +hideAllTips: "「ヒントとコツ」は全部表示せんでええ" +defaultImageCompressionLevel_description: "低くすると画質は保てるんやけど、ファイルサイズが増えるで。
高くするとファイルサイズは減らせるんやけど、画質が落ちるで。" +defaultCompressionLevel_description: "低くすると品質は保てるんやけど、ファイルサイズが増えるで。
高くするとファイルサイズは減らせるんやけど、品質が落ちるで。" +inMinutes: "分" +inDays: "日" +safeModeEnabled: "セーフモードがオンになってるで" +pluginsAreDisabledBecauseSafeMode: "セーフモードがオンやから、プラグインは全部無効化されてるで。" +customCssIsDisabledBecauseSafeMode: "セーフモードがオンやから、カスタムCSSは適用されてへんで。" +themeIsDefaultBecauseSafeMode: "セーフモードがオンの間はデフォルトのテーマを使うで。セーフモードをオフにれば元に戻るで。" +thankYouForTestingBeta: "ベータ版使うてくれておおきに!" +widgets: "ウィジェット" +deviceInfoDescription: "なんか技術的なことで分からんこと聞くときは、下の情報も一緒に書いてもらえると、こっちも分かりやすいし、はよ直ると思います。" +youAreAdmin: "あんた、管理者やで" +presets: "プリセット" +_imageEditing: + _vars: + filename: "ファイル名" +_imageFrameEditor: + tip: "画像にフレームとかメタデータを入れたラベルとかを付け足していい感じにできるで。" + header: "ヘッダー" + font: "フォント" + fontSerif: "セリフ" + fontSansSerif: "サンセリフ" + quitWithoutSaveConfirm: "保存せずに終わってもええんか?" + failedToLoadImage: "あかん、画像読み込まれへんわ" _chat: + noMessagesYet: "まだメッセージはあらへんで" + individualChat_description: "特定のユーザーとサシでチャットできるで。" + roomChat_description: "複数人でチャットできるで。\nあと、個人チャットを許可してへんユーザーとでも、相手がええって言うならチャットできるで。" + inviteUserToChat: "ユーザーを招待してチャットを始めてみ" invitations: "来てや" + noInvitations: "招待はあらへんで" noHistory: "履歴はないわ。" + noRooms: "ルームはあらへんで" + join: "入る" + ignore: "ほっとく" + leave: "グループから抜ける" members: "メンバーはん" home: "ホーム" send: "送信" + deleteRoom: "ルームをほかす" + chatNotAvailableForThisAccountOrServer: "このサーバー、もしくはこのアカウントでチャットが有効にされてへんで。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、もしくはこのアカウントでチャットが読み取り専用になっとるわ。新しく書き込んだり、チャットルームを作ったり参加したりはできへんで。" + chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えんくなっとるみたいやわ。" + cannotChatWithTheUser: "このユーザーとのチャットを開始できへんみたいやわ" + cannotChatWithTheUser_description: "チャットが使えん状態になっとるか、相手がチャットを開放してへんみたいやわ。" + youAreNotAMemberOfThisRoomButInvited: "あんたはこのルームの参加者ちゃうけど、招待が届いとるで。参加するんやったら、招待を承認してな。" + doYouAcceptInvitation: "招待を承認してもええんか?" + chatWithThisUser: "チャットしよか" + thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのチャットしか受け付けとらんみたいやわ。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしとるユーザーからのチャットしか受け付けとらんみたいやわ。" + thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのチャットしか受け付けとらんみたいやわ。" + thisUserNotAllowedChatAnyone: "このユーザーは誰からのチャットも受け付けとらんみたいやわ。" + chatAllowedUsers: "チャットしてもええ相手" + chatAllowedUsers_note: "自分からチャットメッセージを送った相手やったらこの設定に関わらずチャットできるで。" + _chatAllowedUsers: + followers: "自分のフォロワーだけ" + following: "自分がフォローしとるユーザーだけ" + mutual: "相互フォローのユーザーだけ" + none: "誰もかもあかん" +_emojiPalette: + enableSyncBetweenDevicesForPalettes: "パレットのデバイス間同期をつけとく" + paletteForMain: "メインで使うパレット" + paletteForReaction: "リアクションで使うパレット" _settings: + driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードするときの設定ができるで。" + pluginBanner: "プラグインを使うとクライアントの機能を拡張できるねん。プラグインのインストール、個別の設定と管理ができるで。" + notificationsBanner: "サーバーから受け取る通知の種類とか範囲、プッシュ通知の設定ができるで。" webhook: "Webhook" + serviceConnectionBanner: "外部のアプリ・サービスと連携するのに使うとるアクセストークンとかWebhookの管理と設定ができるで。" + accountDataBanner: "アカウントデータのアーカイブをエクスポート/インポートして管理できるで。" + muteAndBlockBanner: "見せんでええコンテンツの設定とか、特定のユーザーからのアクションを制限する設定と管理ができるで。" + accessibilityBanner: "クライアントの視覚や動作に関わるパーソナライズをして、よりええ感じに使えるように設定できるで。" + privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制とかアカウントのプライバシーに関わる設定ができるで。" + securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーとかアカウントのセキュリティに関わる設定ができるで。" + preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定ができるで。" + appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関わる設定ができるで。" + soundsBanner: "クライアントで流すサウンドの設定ができるで。" + makeEveryTextElementsSelectable: "全部のテキスト要素を選択できるようにする" + makeEveryTextElementsSelectable_description: "これをつけると、場面によったら使いにくくなるかもしれん。" + useStickyIcons: "アイコンがスクロールにひっつくようにする" + enablePullToRefresh_description: "マウスやったら、ホイールを押し込みながらドラッグしてな。" + realtimeMode_description: "サーバーと接続を確立して、リアルタイムでコンテンツを更新するで。通信量とバッテリーの消費が多くなるかもしれへん。" + contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されるんやけど、そのぶんパフォーマンスが落ちるし、通信量とバッテリーの消費も増えるねん。" + contentsUpdateFrequency_description2: "リアルタイムモードをつけてるんやったら、この設定がどうであれリアルタイムでコンテンツが更新されるで。" + emojiPaletteBanner: "絵文字ピッカーに置いとくプリセットをパレットっていうので登録したり、ピッカーの見た目を変えたりできるで。" +_preferencesProfile: + profileNameDescription: "このデバイスはなんて呼んだらええんや?" +_preferencesBackup: + noBackupsFoundTitle: "バックアップが見つからへんね" + noBackupsFoundDescription: "自動で作られたバックアップは見つからんかったけど、バックアップファイルを手動で保存してるんやったら、それをインポートして復元できるで。" + selectBackupToRestore: "復元するバックアップを選んでや" + youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効するんやったらプロファイル名の設定が必要やな。" + autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になってへんで。" + backupFound: "設定のバックアップがあるみたいやわ" _accountSettings: requireSigninToViewContents: "ログインしてもらってからコンテンツ見てもらう" requireSigninToViewContentsDescription1: "あなたが作成した全部のノートとかのコンテンツを見れるようにするのにログインがいるようにするで。クローラーにいろいろ収集されるんを防げるかもしれん。" @@ -1325,8 +1444,9 @@ _accountSettings: makeNotesFollowersOnlyBefore: "昔のノートをフォロワーだけに見てもらう" makeNotesFollowersOnlyBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがフォロワーのみ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" makeNotesHiddenBefore: "昔のノートを見れんようにする" - makeNotesHiddenBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがフォロワーのみ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" + makeNotesHiddenBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがあんただけ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばんかもしれん。" + mayNotEffectSomeSituations: "これらの制限は簡易的なものやで。リモートサーバーでの閲覧とかモデレーション時とか、一部のシチュエーションでは適用されへんかもしれん。" notesHavePassedSpecifiedPeriod: "決めた時間が経ったノート" notesOlderThanSpecifiedDateAndTime: "決めた日時より前のノート" _abuseUserReport: @@ -1345,6 +1465,7 @@ _delivery: manuallySuspended: "手動停止中" goneSuspended: "サーバー削除のため停止中" autoSuspendedForNotResponding: "サーバー応答せえへんから停止中" + softwareSuspended: "配信停止中のソフトウェアやから停止中" _bubbleGame: howToPlay: "遊び方" hold: "ホールド" @@ -1399,7 +1520,7 @@ _initialTutorial: description: "ここでは、Misskeyのカンタンな使い方とか機能を確かめれんで。" _note: title: "ノートってなんや?" - description: "Misskeyでの投稿は「ノート」って呼ばれてんで。ノートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。" + description: "Misskeyでの投稿は「ノート」って呼ばれてんで。ノートは順々にタイムラインに載ってて、リアルタイムで新しくなってくで。" reply: "返信もできるで。返信の返信もできるから、スレッドっぽく会話をそのまま続けれもするで。" renote: "そのノートを自分のタイムラインに流して共有できるで。テキスト入れて引用してもええな。" reaction: "ツッコミをつけることもできるで。細かいことは次のページや。" @@ -1419,7 +1540,7 @@ _initialTutorial: social: "ホームタイムラインの投稿もローカルタイムラインのも一緒に見れるで。" global: "繋がってる他の全サーバーからの投稿が見れるで。" description2: "それぞれのタイムラインは、いつでも画面上で切り替えられんねん。覚えとき。" - description3: "その他にも、リストタイムラインとかチャンネルタイムラインとかがあんねん。詳しいのは{link}を見とき。" + description3: "その他にも、リストタイムラインとかチャンネルタイムラインとかがあんねん。詳しいのは{link}を見ときや。" _postNote: title: "ノートの投稿設定" description1: "Misskeyにノートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。" @@ -1455,7 +1576,7 @@ _timelineDescription: home: "ホームタイムラインは、あんたがフォローしとるアカウントの投稿だけ見れるで。" local: "ローカルタイムラインは、このサーバーにおる全員の投稿を見れるで。" social: "ソーシャルタイムラインは、ホームタイムラインの投稿もローカルタイムラインのも一緒に見れるで。" - global: "グローバルタイムラインは、繋がっとる他のサーバーの投稿、全部ひっくるめて見れんで。" + global: "グローバルタイムラインは、繋がっとる他のサーバーの投稿、全部ひっくるめて見れるで。" _serverRules: description: "新規登録前に見せる、サーバーのカンタンなルールを決めるで。内容は使うための決め事の要約がええと思うわ。" _serverSettings: @@ -1471,11 +1592,21 @@ _serverSettings: fanoutTimelineDbFallback: "データベースにフォールバックする" fanoutTimelineDbFallbackDescription: "有効にしたら、タイムラインがキャッシュん中に入ってないときにDBにもっかい問い合わせるフォールバック処理ってのをやっとくで。切ったらフォールバック処理をやらんからサーバーはもっと軽くなんねんけど、タイムラインの取得範囲がちょっと減るで。" reactionsBufferingDescription: "有効にしたら、リアクション作るときのパフォーマンスがすっごい上がって、データベースへの負荷が減るで。代わりに、Redisのメモリ使用は増えるで。" + remoteNotesCleaning_description: "つけると、参照されてへん古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑えてくれるで。" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定するで。" openRegistration: "アカウントの作成をオープンにする" - openRegistrationWarning: "登録を解放するのはリスクが伴うで。サーバーをいっつも監視して、なんか起きたらすぐに対応できるんやったら、オンにしてもええと思う。" + openRegistrationWarning: "登録を解放するのはリスクあるで。サーバーをいっつも監視して、なんか起きたらすぐに対応できるんやったら、オンにしてもええと思うけどな。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターがおらんかったら、スパムを防ぐためにこの設定は勝手に切られるで。" + deliverSuspendedSoftwareDescription: "脆弱性とかの理由で、サーバーのソフトウェアの名前とバージョンの範囲を決めて配信を止められるで。このバージョン情報はサーバーが提供したものやから、信頼性は保証されへん。バージョン指定には semver の範囲指定が使えるねんけど、>= 2024.3.1と指定すると 2024.3.1-custom.0 みたいなカスタムバージョンが含まれへんから、>= 2024.3.1-0 みたいに prerelease を指定するとええかもしれへんな。" + singleUserMode_description: "このサーバーを使うとるんが自分だけなんやったら、このモードを有効にすると動作がええ感じになるで。" + signToActivityPubGet_description: "通常はつけといてな。連合の通信に関わる問題があるんやったら、無効にすると改善するかもしれへんけど、逆にサーバーによっては通信ができんくなることがあるで。" + proxyRemoteFiles_description: "つけると、リモートのファイルをプロキシして提供するで。画像のサムネイル生成とかユーザーのプライバシー保護にええな。" + allowExternalApRedirect_description: "つけると、他のサーバーがうちのサーバーを通して第三者のコンテンツを照会できるようになるんやけど、コンテンツのなりすましが発生するかもしれへん。" + userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツとかが、うちのサーバー経由で図らずもインターネットに公開されてまうことによるトラブルを防止できたりするで。" + userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受け取ったリモートのコンテンツを含め、サーバー内の全部のコンテンツを何でもかんでもインターネットに公開するのはリスクを伴うねん。特に、分散型の特性を知らん閲覧者にとっては、リモートのコンテンツやったとしてもサーバー内で作られたコンテンツやと誤認してまうかもしれへんから、注意が必要やな。" + restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直すん?" + restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされるで。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに引っ越す" moveFromSub: "別のアカウントへエイリアスを作る" @@ -1772,6 +1903,7 @@ _role: descriptionOfIsExplorable: "オンにしたらロールの面子一覧が「みつける」で公開されるし、ロールのタイムラインが使えるようになるで。" displayOrder: "表示順" descriptionOfDisplayOrder: "数がでかいほど、UI上で先に表示されるで。" + preserveAssignmentOnMoveAccount_description: "つけると、このロールがのっかったアカウントが引っ越したときに、引っ越し先アカウントにもこのロールがのっかるようになるで。" canEditMembersByModerator: "モデレーターがメンバーいじるのを許す" descriptionOfCanEditMembersByModerator: "オンにすると、管理者だけやなくてモデレーターもこのロールにユーザーを入れたり抜いたりできるで。オフにすると管理者だけしかやれへんくなるで。" priority: "優先度" @@ -1812,6 +1944,8 @@ _role: canImportFollowing: "フォローのインポートを許す" canImportMuting: "ミュートのインポートを許す" canImportUserLists: "リストのインポートを許す" + uploadableFileTypes_caption: "MIMEタイプを指定してや。改行で区切って複数指定もできるし、アスタリスク(*)でワイルドカード指定もできるで。(例: image/*)" + uploadableFileTypes_caption2: "ファイルによっては種別がわからんこともあるで。そないなファイルを許可するんやったら {x} を指定に追加してな。" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -1857,7 +1991,7 @@ _signup: emailSent: "さっき入れたメアド({email})宛に確認メールを送ったで。メールに書かれたリンク押してアカウント作るの終わらしてな。\nメールの認証リンクの期限は30分や。" _accountDelete: accountDelete: "アカウントの削除" - mayTakeTime: "アカウント消すんはサーバーが重いんやって。やから作ったコンテンツとか上げたファイルの数が多いと消し終わるまでに時間がかかるかもしれへん。" + mayTakeTime: "アカウント消すんはサーバーに負荷かかるんやって。やから、作ったコンテンツとか上げたファイルの数が多いと消し終わるまでに時間がかかるかもしれんわ。" sendEmail: "アカウントの消し終わるときは、登録してたメアドに通知するで。" requestAccountDelete: "アカウント削除頼む" started: "削除処理が始まったで。" @@ -2011,7 +2145,7 @@ _theme: navIndicator: "サイドバーのインジケーター" link: "リンク" hashtag: "ハッシュタグ" - mention: "メンション" + mention: "あんた宛て" mentionMe: "うち宛てのメンション" renote: "Renote" modalBg: "モーダルの背景" @@ -2028,7 +2162,6 @@ _theme: buttonBg: "ボタンの背景" buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" - driveFolderBg: "ドライブフォルダーの背景" badge: "バッジ" messageBg: "チャットの背景" fgHighlighted: "強調されとる文字" @@ -2037,6 +2170,7 @@ _sfx: noteMy: "ノート(自分)" notification: "通知" reaction: "ツッコミ選んどるとき" + chatMessage: "チャットしよか" _soundSettings: driveFile: "ドライブん中の音使う" driveFileWarn: "ドライブん中のファイル選びや" @@ -2197,6 +2331,7 @@ _auth: scopeUser: "以下のユーザーとしていじってるで" pleaseLogin: "アプリにアクセスさせるんやったら、ログインしてや。" byClickingYouWillBeRedirectedToThisUrl: "アクセスを許したら、自動で下のURLに遷移するで" + alreadyAuthorized: "このアプリはもうアクセスを許可してるみたいやで。" _antennaSources: all: "みんなのノート" homeTimeline: "フォローしとるユーザーのノート" @@ -2242,6 +2377,16 @@ _widgets: chooseList: "リストを選ぶ" clicker: "クリッカー" birthdayFollowings: "今日誕生日のツレ" + chat: "チャットしよか" +_widgetOptions: + showHeader: "ヘッダー出す" + height: "高さ" + _button: + colored: "色付き" + _clock: + size: "大きさ" + _birthdayFollowings: + period: "期間" _cw: hide: "隠す" show: "続き見して!" @@ -2281,9 +2426,19 @@ _visibility: disableFederation: "連合なし" disableFederationDescription: "他サーバーへは送らんとくわ" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "アップロードされてへんファイルがあるんやけど、ほかしてフォームを閉じてもええんか?" + uploaderTip: "ファイルはまだアップロードされてへんで。ファイルのメニューから、リネームとか画像のクロップ、ウォーターマークをのっける、圧縮するかどうかなんかを設定できるで。ファイルはノートを投稿するときに自動でアップロードされるで。" replyPlaceholder: "このノートに返信..." quotePlaceholder: "このノートを引用..." channelPlaceholder: "チャンネルに投稿..." + _howToUse: + toolbar_description: "ファイルとかアンケートを付けたり、注釈とかハッシュタグを書いたり、絵文字とかメンションとかを付け足したりできるで。" + account_description: "投稿するアカウントを変えたり、アカウントに保存した下書きとか予約投稿とかを見れるで。" + visibility_title: "公開範囲" + visibility_description: "ノートを誰に見せたいかはここで切り替えてな。" + menu_title: "メニュー" + menu_description: "下書きに保存したり、投稿の予約したり、リアクションの受け入れ設定とか…なんか色々できるで。" + submit_description: "ノートを投稿するときはここ押してな。Ctrl + Enter / Cmd + Enter でも投稿できるで。" _placeholders: a: "いまどないしとるん?" b: "何かあったん?" @@ -2429,9 +2584,12 @@ _notification: youReceivedFollowRequest: "フォロー許可してほしいみたいやな" yourFollowRequestAccepted: "フォローさせてもろたで" pollEnded: "アンケートの結果が出たみたいや" + scheduledNotePosted: "予約ノートが投稿されたで" + scheduledNotePostFailed: "予約ノート投稿できんかったで" newNote: "さらの投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されたで" + chatRoomInvitationReceived: "チャットルームへ招待されたで" emptyPushNotificationMessage: "プッシュ通知の更新をしといたで" achievementEarned: "実績を獲得しとるで" testNotification: "通知テスト" @@ -2451,7 +2609,7 @@ _notification: all: "すべて" note: "あんたらの新規投稿" follow: "フォロー" - mention: "メンション" + mention: "あんた宛て" reply: "リプライ" renote: "リノート" quote: "引用" @@ -2501,6 +2659,7 @@ _deck: mentions: "あんた宛て" direct: "ダイレクト" roleTimeline: "ロールタイムライン" + chat: "チャットしよか" _dialog: charactersExceeded: "最大の文字数を上回っとるで!今は {current} / 最大でも {max}" charactersBelow: "最小の文字数を下回っとるで!今は {current} / 最低でも {min}" @@ -2621,7 +2780,7 @@ _externalResourceInstaller: _errors: _invalidParams: title: "" - description: "" + description: "外部サイトからデータを持ってくるのに欲しい情報が足らへんみたいやわ。URLは合っとる?" _resourceTypeNotSupported: title: "" description: "" @@ -2651,11 +2810,12 @@ _dataSaver: _avatar: title: "アイコンの絵" description: "アイコン画像のアニメが止まるで。普通の画像よりもデータ量がでかいから、もっと通信量を節約できるねん。" - _urlPreview: - title: "URLプレビューのサムネイル画像" - description: "URLプレビューのサムネイル画像が読み込まへんなるで。" + _urlPreviewThumbnail: + description: "URLプレビューのサムネイル画像が読み込まれへんくなるで。" + _disableUrlPreview: + description: "URLプレビュー機能を切るで。サムネイル画像だけと違って、リンク先の情報の読み込み自体を削減できるで。" _code: - title: "コードハイライト" + title: "コードハイライトは表示せんでええ" description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。" _hemisphere: N: "北半球" @@ -2711,6 +2871,7 @@ _offlineScreen: _urlPreviewSetting: title: "URLプレビューの設定" enable: "URLプレビューを有効にする" + allowRedirectDescription: "入力されたURLがリダイレクトされるとき、そのリダイレクト先をたどってプレビューを表示するかどうかを設定できるで。無効にするとサーバーリソースを節約できるんやけど、リダイレクト先の内容は表示されへんくなるで。" timeout: "プレビュー取得時のタイムアウト(ms)" timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されへんで。" maximumContentLength: "Content-Lengthの最大値(byte)" @@ -2784,10 +2945,6 @@ _customEmojisManager: uploadSettingDescription: "この画面で絵文字アップロードするときの動きを設定できるで。" directoryToCategoryLabel: "ディレクトリ名を\"category\"に入力する" directoryToCategoryCaption: "ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を\"category\"に入力します。" - emojiInputAreaCaption: "どれかの方法で登録する絵文字を選択して。" - emojiInputAreaList1: "この枠に画像ファイルかディレクトリをドラッグ&ドロップ" - emojiInputAreaList2: "このリンクをクリックしてPCから選択する" - emojiInputAreaList3: "このリンクをクリックしてドライブから選択する" confirmRegisterEmojisDescription: "リストに表示されてる絵文字を新たなカスタム絵文字として登録するで。ほんまにええか? (サーバーがしんどくなるから、一回で登録できる絵文字は{count}件までやで)" confirmClearEmojisDescription: "編集内容をほかして、リストに表示されている絵文字をクリアするで。ほんまにええか?" confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードするで。ほんまにええか?" @@ -2859,3 +3016,85 @@ _search: searchScopeAll: "みんな" searchScopeLocal: "ローカル" searchScopeUser: "ユーザー指定" + pleaseEnterServerHost: "サーバーのホストはどないするん?" + pleaseSelectUser: "ユーザーを選んでや" +_serverSetupWizard: + installCompleted: "Misskeyのインストールが終わったで!" + firstCreateAccount: "最初は、管理者アカウントを作成しよか。" + accountCreated: "管理者アカウントができたで!" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "このウィザードで簡単にええ感じのサーバーの設定ができるで。" + settingsYouMakeHereCanBeChangedLater: "ここでの設定は、あとからでも変えられるで。" + howWillYouUseMisskey: "Misskeyをどんな感じに使うん?" + _use: + single_youCanCreateMultipleAccounts: "お一人様サーバーとして運用するとしても、アカウントは必要に応じて複数作れるで。" + openServerAdvice: "不特定多数の利用者を受け入れるには相応のリスクがあるで。トラブルに対処できるよう、ちゃんとしたモデレーション体制で運営しいや。" + openServerAntiSpamAdvice: "うちのサーバーがスパムの踏み台にならへんように、reCAPTCHAとかのアンチボット機能を使う、みたいなセキュリティ対策もしっかり考えてな。" + howManyUsersDoYouExpect: "どれくらいの人数を考えとるん?" + largeScaleServerAdvice: "大規模なサーバーやったら、ロードバランシングとかデータベースのレプリケーションみたいな、高度なインフラストラクチャーの知識が必要になるかもしれへんわ。" + doYouConnectToFediverse: "Fediverseと接続するんやっけ?" + doYouConnectToFediverse_description1: "分散型サーバーでできたネットワーク(Fediverse)に繋げると、他のサーバーと相互にコンテンツのやり取りができるようになるで。" + doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれるな。" + youCanConfigureMoreFederationSettingsLater: "連合してもええサーバーの指定とか、高度な設定も後でできるで。" + remoteContentsCleaning_description: "連合すると、ぎょうさんコンテンツを受け取り続けることになるねん。自動クリーニングをつけると、参照されてない古いコンテンツを自動でサーバーからほかして、ストレージを節約できるで。" + adminInfo_description: "問い合わせを受け付けるのに使う管理者情報を設定しよか。" + adminInfo_mustBeFilled: "オープンサーバー、もしくは連合を入れとるんやったら必ず入力せなあかんで。" + followingSettingsAreRecommended: "こういう設定がええかもな" + settingsCompleted: "設定が終わったで!" + settingsCompleted_description: "お疲れさん。準備ができたから、さっそくサーバーを使い始められるで。" + settingsCompleted_description2: "細かいサーバー設定は、「コントロールパネル」を見てみてな。" + _donationRequest: + text1: "Misskeyは有志で開発されとる無料のソフトウェアやで。" + text2: "今後も開発を続けられるように、よかったらぜひカンパをお願いするわ。" + text3: "支援者向け特典もあるで!" +_uploader: + abortConfirm: "アップロードされてへんファイルがあるんやけど、やめてもええんか?" + doneConfirm: "アップロードされてへんファイルがあるんやけど、完了してもええんか?" + maxFileSizeIsX: "アップロードできるファイルサイズは{x}までやで。" + tip: "ファイルはまだアップロードされてへんで。このダイアログで、アップロードする前に確認・リネーム・圧縮・クロッピングとかをできるで。準備が出来たら、「アップロード」ボタンを押してアップロードしてな。" +_clientPerformanceIssueTip: + title: "バッテリーようさん食うなぁと思ったら" + makeSureDisabledAdBlocker: "アドブロッカーを切ってみてや" + makeSureDisabledAdBlocker_description: "アドブロッカーはパフォーマンスに影響があるかもしれへん。OSの機能とかブラウザの機能・アドオンとかでアドブロッカーが有効になってないか確認してや。" + makeSureDisabledCustomCss: "カスタムCSSを無効にしてみてや" + makeSureDisabledCustomCss_description: "スタイルを上書きするとパフォーマンスに影響があるかもしれへん。カスタムCSSとか、スタイルを上書きする拡張機能が有効になってないか確認してや。" + makeSureDisabledAddons: "拡張機能を無効にしてみてや" + makeSureDisabledAddons_description: "なんかの拡張機能がクライアントの動作にちょっかいをかけてパフォーマンスに影響を与えてるかもしれへん。ブラウザの拡張機能を無効にして良くなるか確認してや。" +_clip: + tip: "クリップは、ノートをまとめられる機能やで。" +_userLists: + tip: "好きなユーザーを含むリストを作れるねん。作ったリストはタイムラインとして表示できるで。" +_watermarkEditor: + tip: "画像にクレジット情報とかのウォーターマークをのっけられるで。" + quitWithoutSaveConfirm: "保存せずに終わってもええんか?" + driveFileTypeWarn: "このファイルは対応しとらへん" + driveFileTypeWarnDescription: "画像ファイルを選んでや" + opacity: "不透明度" + scale: "大きさ" + text: "テキスト" + position: "位置" + type: "タイプ" + image: "画像" + advanced: "高度" + angle: "角度" + failedToLoadImage: "あかん、画像読み込まれへんわ" +_imageEffector: + discardChangesConfirm: "変更をせんで終わるか?" + failedToLoadImage: "あかん、画像読み込まれへんわ" + _fxProps: + angle: "角度" + scale: "大きさ" + size: "大きさ" + offset: "位置" + color: "色" + opacity: "不透明度" + lightness: "明るさ" +_drafts: + cannotCreateDraftAnymore: "下書きはこれ以上は作れへんな。" + cannotCreateDraft: "この内容で下書きは作れへんな。" + delete: "下書きをほかす" + deleteAreYouSure: "下書きをほかしてもええか?" + noDrafts: "下書きはあらへん" +_qr: + showTabTitle: "表示" + shareText: "Fediverseでフォローしてな!" + raw: "テキスト" diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index d4aa36fa70..27d20f47d1 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -57,6 +57,10 @@ searchByGoogle: "Nadi" file: "Ifuyla" account: "Imiḍan" replies: "Err" +_imageFrameEditor: + font: "Tasefsit" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _email: _follow: title: "Yeṭṭafaṛ-ik·em-id" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index 222599572a..cdd9a5b2ae 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -44,6 +44,7 @@ showMore: "ಇನ್ನಷ್ಟು ನೋಡು" youGotNewFollower: "ಹಿಂಬಾಲಿಸಿದರು" receiveFollowRequest: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಬಂದಿದೆ" followRequestAccepted: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಸ್ವೀಕರಿಸಲಾಯಿತು" +mention: "ಹೆಸರಿಸಿದ" mentions: "ಹೆಸರಿಸಿದ" directNotes: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" importAndExport: "ಆಮದು/ರಫ್ತು" @@ -65,6 +66,9 @@ replies: "ಉತ್ತರಿಸು" _email: _follow: title: "ಹಿಂಬಾಲಿಸಿದರು" +_theme: + keys: + mention: "ಹೆಸರಿಸಿದ" _sfx: notification: "ಅಧಿಸೂಚನೆಗಳು" _widgets: @@ -73,11 +77,14 @@ _widgets: timeline: "ಸಮಯಸಾಲು" _cw: show: "ಇನ್ನಷ್ಟು ನೋಡು" +_visibility: + specified: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" _profile: username: "ಬಳಕೆಹೆಸರು" _notification: youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು" _types: + mention: "ಹೆಸರಿಸಿದ" login: "ಪ್ರವೇಶ" _actions: reply: "ಉತ್ತರಿಸು" @@ -86,3 +93,4 @@ _deck: notifications: "ಅಧಿಸೂಚನೆಗಳು" tl: "ಸಮಯಸಾಲು" mentions: "ಹೆಸರಿಸಿದ" + direct: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index fb21b47fac..b44bd2f482 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -224,7 +224,6 @@ noUsers: "사용자가 어ᇝ십니다" editProfile: "프로필 적기" noteDeleteConfirm: "요 노트럴 뭉캡니꺼?" pinLimitExceeded: "더 몬 붙입니다" -intro: "Misskey럴 다 깔앗십니다! 간리자 게정얼 맨걸어 보입시다." done: "햇어예" processing: "처리하고 잇어예" preview: "미리보기" @@ -652,6 +651,9 @@ renotes: "리노트" attach: "옇기" surrender: "아이예" information: "정보" +_imageEditing: + _vars: + filename: "파일 이럼" _chat: invitations: "초대하기" noHistory: "기록이 없십니다" @@ -746,7 +748,7 @@ _menuDisplay: _theme: description: "설멩" keys: - mention: "멘션" + mention: "받언 멘션" renote: "리노트" _sfx: note: "새 노트" @@ -776,6 +778,7 @@ _cw: _visibility: home: "덜머리" followers: "팔로워" + specified: "쪽지 서기" _postForm: _placeholders: e: "옇다 서 주이소" @@ -810,7 +813,7 @@ _notification: newNote: "새 걸" _types: follow: "팔로잉" - mention: "멘션" + mention: "받언 멘션" renote: "리노트" quote: "따오기" reaction: "반엉" @@ -825,6 +828,7 @@ _deck: antenna: "안테나" list: "리스트" mentions: "받언 멘션" + direct: "쪽지 서기" _webhookSettings: name: "이럼" _abuseReport: @@ -849,3 +853,7 @@ _remoteLookupErrors: _search: searchScopeAll: "말캉" searchScopeUser: "사용자 지정" +_watermarkEditor: + image: "이미지" +_qr: + showTabTitle: "보기" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index fb7db80186..09ffe75f82 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -83,6 +83,8 @@ files: "파일" download: "다운로드" driveFileDeleteConfirm: "‘{name}’ 파일을 삭제하시겠습니까? 이 파일을 사용하는 일부 콘텐츠도 삭제됩니다." unfollowConfirm: "{name}님을 언팔로우하시겠습니까?" +cancelFollowRequestConfirm: "{name}(으)로의 팔로우 신청을 취소하시겠습니까?" +rejectFollowRequestConfirm: "{name}(으)로부터의 팔로우 신청을 거부하시겠습니까?" exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다." importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다." lists: "리스트" @@ -220,6 +222,7 @@ silenceThisInstance: "서버를 사일런스" mediaSilenceThisInstance: "서버의 미디어를 사일런스" operations: "작업" software: "소프트웨어" +softwareName: "소프트웨어 이름" version: "버전" metadata: "메타데이터" withNFiles: "{n}개의 파일" @@ -250,9 +253,9 @@ noUsers: "아무도 없습니다" editProfile: "프로필 수정" noteDeleteConfirm: "이 노트를 삭제하시겠습니까?" pinLimitExceeded: "더 이상 고정할 수 없습니다." -intro: "Misskey의 설치가 완료되었습니다! 관리자 계정을 생성해주세요." done: "완료" processing: "처리중" +preprocessing: "준비중" preview: "미리보기" default: "기본값" defaultValueIs: "기본값: {value}" @@ -298,8 +301,10 @@ uploadFromUrl: "URL 업로드" uploadFromUrlDescription: "업로드하려는 파일의 URL" uploadFromUrlRequested: "업로드를 요청했습니다" uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다." +uploadNFiles: "{n}개의 파일을 업로" explore: "둘러보기" messageRead: "읽음" +readAllChatMessages: "모든 메시지를 읽은 상태로 표시" noMoreHistory: "이것보다 과거의 기록이 없습니다" startChat: "채팅을 시작하기" nUsersRead: "{n}명이 읽음" @@ -326,11 +331,13 @@ dark: "다크" lightThemes: "밝은 테마" darkThemes: "어두운 테마" syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화" +switchDarkModeManuallyWhenSyncEnabledConfirm: "'{x}'가 켜져 있습니다. 동기화를 끄고 수동으로 모드를 변경하겠습니까?" drive: "드라이브" fileName: "파일명" selectFile: "파일 선택" selectFiles: "파일 선택" selectFolder: "폴더 선택" +unselectFolder: "폴더 선택 해제" selectFolders: "폴더 선택" fileNotSelected: "파일을 선택하지 않았습니다" renameFile: "파일 이름 변경" @@ -343,6 +350,7 @@ addFile: "파일 추가" showFile: "파일 표시하기" emptyDrive: "드라이브가 비어 있습니다" emptyFolder: "폴더가 비어 있습니다" +dropHereToUpload: "업로드할 파일을 여기로 드롭하십시오" unableToDelete: "삭제할 수 없습니다" inputNewFileName: "바꿀 파일명을 입력해 주세요" inputNewDescription: "새 캡션을 입력해 주세요" @@ -575,8 +583,10 @@ showFixedPostForm: "타임라인 상단에 글 입력란을 표시" showFixedPostFormInChannel: "채널 타임라인 상단에 글 입력란을 표시" withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답글을 타임라인에 나오게 하기" newNoteRecived: "새 노트가 있습니다" +newNote: "새로운 노트" sounds: "소리" sound: "소리" +notificationSoundSettings: "알림 설정" listen: "듣기" none: "없음" showInPage: "페이지로 보기" @@ -768,6 +778,7 @@ lockedAccountInfo: "팔로우를 승인으로 승인받더라도 노트의 공 alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정" loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시" disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음" +disableShowingAnimatedImages_caption: "이 설정에 상관없이 애니메이션 이미지가 재생되지 않을 때는 브라우저·OS의 액티비티 설정이나 절전 모드 설정 등이 간섭하고 있는 경우가 있습니다." highlightSensitiveMedia: "미디어가 민감한 내용이라는 것을 알기 쉽게 표시" verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요." notSet: "설정되지 않음" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "이 기능은 실험적인 기능입니다. 사양 developer: "개발자" makeExplorable: "계정을 쉽게 발견하도록 하기" makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다." -showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시" duplicate: "복제" left: "왼쪽" center: "가운데" @@ -792,6 +802,7 @@ wide: "넓게" narrow: "좁게" reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" needReloadToApply: "변경 사항은 새로고침하면 적용됩니다." +needToRestartServerToApply: "변경 사항은 새로고침이 필요합니다." showTitlebar: "타이틀 바를 표시하기" clearCache: "캐시 비우기" onlineUsersCount: "{n}명이 접속 중" @@ -979,6 +990,7 @@ document: "문서" numberOfPageCache: "페이지 캐시 수" numberOfPageCacheDescription: "숫자가 클 수록 편리성이 높아지지만, 시스템 자원과 메모리를 더 많이 사용합니다." logoutConfirm: "로그아웃 하시겠습니까?" +logoutWillClearClientData: "로그아웃하면 클라이언트의 설정 데이터가 브라우저에서 지워지게 됩니다. 다시 로그인할 때 설정 데이터를 복원할 수 있도록 하려면 설정 자동 백업을 활성화하세요." lastActiveDate: "마지막 이용" statusbar: "상태바" pleaseSelect: "선택해 주세요" @@ -997,6 +1009,7 @@ failedToUpload: "업로드 실패" cannotUploadBecauseInappropriate: "이 파일은 부적절한 내용을 포함한다고 판단되어 업로드할 수 없습니다." cannotUploadBecauseNoFreeSpace: "드라이브 용량이 부족하여 업로드할 수 없습니다." cannotUploadBecauseExceedsFileSizeLimit: "파일 크기가 너무 크기 때문에 업로드할 수 없습니다." +cannotUploadBecauseUnallowedFileType: "허가되지 않은 유형의 파일이기에 업로드할 수 없습니다." beta: "베타" enableAutoSensitive: "자동 NSFW 탐지" enableAutoSensitiveDescription: "이용 가능할 경우 기계학습을 통해 자동으로 미디어 NSFW를 설정합니다. 이 기능을 해제하더라도, 서버 정책에 따라 자동으로 설정될 수 있습니다." @@ -1012,6 +1025,9 @@ pushNotificationAlreadySubscribed: "푸시 알림이 이미 켜져 있습니다" pushNotificationNotSupported: "브라우저나 서버에서 푸시 알림이 지원되지 않습니다" sendPushNotificationReadMessage: "푸시 알림이나 메시지를 읽은 뒤 푸시 알림을 삭제" sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」이라는 알림이 잠깐 표시됩니다. 기기의 전력 소비량이 증가할 수 있습니다." +pleaseAllowPushNotification: "브라우저의 알림 설정을 허가해 주십시오." +browserPushNotificationDisabled: "알림 송신 권한 얻기에 실패했습니다." +browserPushNotificationDisabledDescription: "{serverName}에서의 알림 송신 권한이 없습니다. 브라우저의 설정에서 알림을 허가해 다시 시도해 주십시오." windowMaximize: "최대화" windowMinimize: "최소화" windowRestore: "복구" @@ -1048,6 +1064,7 @@ permissionDeniedError: "작업이 거부되었습니다" permissionDeniedErrorDescription: "이 작업을 수행할 권한이 없습니다." preset: "프리셋" selectFromPresets: "프리셋에서 선택" +custom: "커스텀" achievements: "도전 과제" gotInvalidResponseError: "서버의 응답이 올바르지 않습니다" gotInvalidResponseErrorDescription: " 서버가 다운되었거나 점검중일 가능성이 있습니다. 잠시후에 다시 시도해 주십시오." @@ -1086,6 +1103,7 @@ prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, hiddenTags: "숨긴 해시태그" hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다." notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다." +usersSearchNotAvailable: "유저 검색을 이용하실 수 없습니다." license: "라이선스" unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?" myClips: "내 클립" @@ -1160,6 +1178,7 @@ installed: "설치됨" branding: "브랜딩" enableServerMachineStats: "서버의 머신 사양을 공개하기" enableIdenticonGeneration: "유저마다의 Identicon 생성 유효화" +showRoleBadgesOfRemoteUsers: "리모트 유저의 역할 배지 표시" turnOffToImprovePerformance: "이 기능을 끄면 성능이 향상될 수 있습니다." createInviteCode: "초대 코드 생성" createWithOptions: "옵션을 지정하여 생성" @@ -1236,9 +1255,8 @@ showAvatarDecorations: "아바타 장식 표시" releaseToRefresh: "놓아서 새로고침" refreshing: "새로고침 중" pullDownToRefresh: "아래로 내려서 새로고침" -disableStreamingTimeline: "타임라인의 실시간 갱신을 무효화하기" useGroupedNotifications: "알림을 그룹화하고 표시" -signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." +emailVerificationFailedError: "메일 주소 확인에 실패했습니다. 확인에 필요한 URL의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." doReaction: "리액션 추가" code: "문자열" @@ -1308,6 +1326,8 @@ availableRoles: "사용 가능한 역할" acknowledgeNotesAndEnable: "활성화 하기 전에 주의 사항을 확인했습니다." federationSpecified: "이 서버는 화이트 리스트 제도로 운영 중 입니다. 정해진 리모트 서버가 아닌 경우 연합되지 않습니다." federationDisabled: "이 서버는 연합을 하지 않고 있습니다. 리모트 서버 유저와 통신을 할 수 없습니다." +draft: "초안" +draftsAndScheduledNotes: "초안과 예약 게시물" confirmOnReact: "리액션할 때 확인" reactAreYouSure: "\" {emoji} \"로 리액션하시겠습니까?" markAsSensitiveConfirm: "이 미디어를 민감한 미디어로 설정하시겠습니까?" @@ -1325,6 +1345,7 @@ restore: "복원" syncBetweenDevices: "장치간 동기화" preferenceSyncConflictTitle: "서버에 설정값이 존재합니다." preferenceSyncConflictText: "동기화를 활성화 한 항목의 설정 값은 서버에 저장되지만, 해당 항목은 이미 서버에 설정 값이 저장되어져 있습니다. 어느 쪽의 설정 값을 덮어씌울까요?" +preferenceSyncConflictChoiceMerge: "병합" preferenceSyncConflictChoiceServer: "서버 설정값" preferenceSyncConflictChoiceDevice: "장치 설정값" preferenceSyncConflictChoiceCancel: "동기화 취소" @@ -1334,6 +1355,8 @@ postForm: "글 입력란" textCount: "문자 수" information: "정보" chat: "채팅" +directMessage: "채팅하기" +directMessage_short: "메시지" migrateOldSettings: "기존 설정 정보를 이전" migrateOldSettings_description: "보통은 자동으로 이루어지지만, 어떤 이유로 인해 성공적으로 이전이 이루어지지 않는 경우 수동으로 이전을 실행할 수 있습니다. 현재 설정 정보는 덮어쓰게 됩니다." compress: "압축" @@ -1344,7 +1367,100 @@ embed: "임베드" settingsMigrating: "설정을 이전하는 중입니다. 잠시 기다려주십시오... (나중에 '환경설정 → 기타 → 기존 설정 정보를 이전'에서 수동으로 이전할 수도 있습니다)" readonly: "읽기 전용" goToDeck: "덱으로 돌아가기" +federationJobs: "연합 작업" +driveAboutTip: "드라이브는 이전에 업로드한 파일 목록을 표시해요.
\n노트에 첨부할 때 다시 사용하거나 나중에 게시할 파일을 미리 업로드할 수 있어요.
\n파일을 삭제하면, 지금까지 그 파일을 사용한 모든 장소(노트, 페이지, 아바타, 배너 등)에서도 보이지 않게 되므로 주의해 주세요. 폴더를 만들고 정리할 수도 있어요.
" +scrollToClose: "스크롤하여 닫기" +advice: "참고" +realtimeMode: "실시간 모드" +turnItOn: "켜기" +turnItOff: "끄기" +emojiMute: "이모티콘 뮤트" +emojiUnmute: "이모티콘 뮤트 해제" +muteX: "{x}를 뮤트" +unmuteX: "{x}의 뮤트를 해제" +abort: "중지" +tip: "팁과 유용한 정보" +redisplayAllTips: "모든 '팁과 유용한 정보'를 재표시" +hideAllTips: "모든 '팁과 유용한 정보'를 비표시" +defaultImageCompressionLevel: "기본 이미지 압축 정도" +defaultImageCompressionLevel_description: "낮추면 화질을 유지합니다만 파일 크기는 증가합니다.
높이면 파일 크기를 줄일 수 있습니다만 화질은 저하됩니다." +defaultCompressionLevel: "기본 압축 정도 " +defaultCompressionLevel_description: "낮추면 품질을 유지합니다만 파일 크기는 증가합니다.
높이면 파일 크기를 줄일 수 있습니다만 품질은 저하됩니다." +inMinutes: "분" +inDays: "일" +safeModeEnabled: "세이프 모드가 활성화돼있습니다" +pluginsAreDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 플러그인은 전부 비활성화됩니다." +customCssIsDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 커스텀 CSS는 적용되지 않습니다." +themeIsDefaultBecauseSafeMode: "세이프 모드가 활성화돼있는 동안에는 기본 테마가 사용됩니다. 세이프 모드를 끄면 원래대로 돌아옵니다." +thankYouForTestingBeta: "베타 버전의 검증에 협력해 주셔서 감사합니다!" +createUserSpecifiedNote: "사용자 지정 노트를 작성" +schedulePost: "게시 예약" +scheduleToPostOnX: "{x}에 게시를 예약합니다." +scheduledToPostOnX: "{x}에 게시가 예약돼있습니다." +schedule: "예약" +scheduled: "예약" +widgets: "위젯" +deviceInfo: "장치 정보" +deviceInfoDescription: "기술적 문의의 경우 아래의 정보를 병기하면 문제의 해결에 도움이 됩니다." +youAreAdmin: "당신은 관리자입니다." +frame: "프레임" +presets: "프리셋" +zeroPadding: "0으로 채우기" +nothingToConfigure: "설정 항목이 없습니다." +_imageEditing: + _vars: + caption: "파일 설명" + filename: "파일명" + filename_without_ext: "확장자가 없는 파일명" + year: "촬영한 해" + month: "촬영한 달" + day: "촬영한 날" + hour: "촬영한 시각(시)" + minute: "촬영한 시각(분)" + second: "촬영한 시각(초)" + camera_model: "카메라 이름" + camera_lens_model: "렌즈 이름" + camera_mm: "초점 거리" + camera_mm_35: "초점 거리(35m판 환산)" + camera_f: "조리개 조절" + camera_s: "셔터 속도" + camera_iso: "ISO 감도" + gps_lat: "위도" + gps_long: "경도" +_imageFrameEditor: + title: "프레임 편집" + tip: "이미지에 프레임이나 메타 데이터를 포함한 라벨을 추가해 장식할 수 있습니다." + header: "헤더" + footer: "꼬리말" + borderThickness: "테두리의 폭" + labelThickness: "라벨의 폭" + labelScale: "라벨의 스케일" + centered: "중앙 정렬" + captionMain: "캡션(대)" + captionSub: "캡션(소)" + availableVariables: "이용 가능한 변수" + withQrCode: "QR 코드" + backgroundColor: "배경색" + textColor: "글꼴 색상" + font: "폰트" + fontSerif: "명조체" + fontSansSerif: "고딕체" + quitWithoutSaveConfirm: "보존하지 않고 종료하시겠습니까?" + failedToLoadImage: "이미지 로드에 실패했습니다." +_compression: + _quality: + high: "고품질" + medium: "중간 품질" + low: "저품질" + _size: + large: "대형" + medium: "중형" + small: "소형" +_order: + newest: "최신 순" + oldest: "오래된 순" _chat: + messages: "메시지" noMessagesYet: "아직 메시지가 없습니다" newMessage: "새로운 메시지" individualChat: "개인 대화" @@ -1377,6 +1493,8 @@ _chat: chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다." cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다" cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다." + youAreNotAMemberOfThisRoomButInvited: "당신은 이 룸의 참가자가 아닙니다만 초대 신청을 받으셨습니다. 참가하려면 초대를 수락해주십시오." + doYouAcceptInvitation: "초대를 수락하시겠습니까?" chatWithThisUser: "채팅하기" thisUserAllowsChatOnlyFromFollowers: "이 유저는 팔로워만 채팅을 할 수 있습니다." thisUserAllowsChatOnlyFromFollowing: "이 유저는 이 유저가 팔로우하는 유저만 채팅을 허용합니다." @@ -1416,10 +1534,26 @@ _settings: makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함" makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 유저의 접근성이 나빠질 수도 있습니다." useStickyIcons: "아이콘이 스크롤을 따라가도록 하기" + enableHighQualityImagePlaceholders: "고화질 이미지의 플레이스홀더를 표시" + uiAnimations: "UI 애니메이션" showNavbarSubButtons: "내비게이션 바에 보조 버튼 표시" ifOn: "켜져 있을 때" ifOff: "꺼져 있을 때" enableSyncThemesBetweenDevices: "기기 간 설치한 테마 동기화" + enablePullToRefresh: "계속해서 갱신" + enablePullToRefresh_description: "마우스에서 휠을 누르면서 드래그해요." + realtimeMode_description: "서버에 접속하고 실시간으로 콘텐츠를 업데이트합니다. 데이터 사용량과 배터리의 소비가 증가할 수 있습니다." + contentsUpdateFrequency: "콘텐츠의 업데이트 빈도" + contentsUpdateFrequency_description: "높을수록 실시간으로 콘텐츠가 업데이트됩니다만, 성능이 저하되고 데이터 사용량과 배터리의 소비가 증가합니다." + contentsUpdateFrequency_description2: "실시간 모드가 켜져 있을 때는 이 설정과 상관없이 실시간으로 콘텐츠가 업데이트됩니다." + showUrlPreview: "URL 미리보기 표시" + showAvailableReactionsFirstInNote: "이용 가능한 리액션을 선두로 표시" + showPageTabBarBottom: "페이지의 탭 바를 아래쪽에 표시" + emojiPaletteBanner: "이모티콘 선택기에 고정 표시되는 프리셋을 팔레트로 등록하거나 선택기의 표시 방법을 커스터마이징할 수 있습니다." + enableAnimatedImages: "애니메이션 이미지 활성화" + settingsPersistence_title: "설정 영구화" + settingsPersistence_description1: "설정 영구화를 활성화하면 설정 정보를 잃어버리는 것을 방지할 수 있습니다." + settingsPersistence_description2: "환경에 따라 활성화되지 않을 수 있습니다." _chat: showSenderName: "발신자 이름 표시" sendOnEnter: "엔터로 보내기" @@ -1427,6 +1561,9 @@ _preferencesProfile: profileName: "프로필 이름" profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요." profileNameDescription2: "예: '메인PC', '스마트폰' 등" + manageProfiles: "프로파일 관리" + shareSameProfileBetweenDevicesIsNotRecommended: "여러 장치에서 동일한 프로필을 공유하는 것은 권장하지 않습니다." + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "여러 장치에서 동기화하고 싶은 설정 항목이 있는 경우에는 개별로 '여러 장치에서 동기화' 옵션을 활성화해 주십시오." _preferencesBackup: autoBackup: "자동 백업" restoreFromBackup: "백업으로 복구" @@ -1436,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "자동 백업을 활성화하려면 프로필 이름을 설정해야 합니다." autoPreferencesBackupIsNotEnabledForThisDevice: "이 장치에서 설정 자동 백업이 활성화되어 있지 않습니다." backupFound: "설정 백업이 발견되었습니다" + forceBackup: "설정 강제 백업" _accountSettings: requireSigninToViewContents: "콘텐츠 열람을 위해 로그인을 필수로 설정하기" requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다." @@ -1465,6 +1603,7 @@ _delivery: manuallySuspended: "수동 정지 중" goneSuspended: "서버 삭제를 이유로 정지 중" autoSuspendedForNotResponding: "서버 응답 없음을 이유로 정지 중" + softwareSuspended: "전달 정지 중인 소프트웨어이므로 정지 중" _bubbleGame: howToPlay: "설명" hold: "홀드" @@ -1591,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기" fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다." reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다." + remoteNotesCleaning: "리모트 서버 노트 자동 정리 " + remoteNotesCleaning_description: "더 이상 사용되지 않는 오래된 리모트 노트를 정기적으로 정리하여, 데이터 베이스의 사용량을 절약할 수 있습니다." + remoteNotesCleaningMaxProcessingDuration: "리모트 노트 자동 정리 최대 실행 시간" + remoteNotesCleaningExpiryDaysForEachNotes: "리모트 노트 저장 최소 일수" inquiryUrl: "문의처 URL" inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다." openRegistration: "회원 가입을 활성화 하기" openRegistrationWarning: "회원 가입을 개방하는 것은 리스크가 따릅니다. 서버를 항상 감시할 수 있고, 문제가 발생했을 때 바로 대응할 수 있는 상태에서만 활성화 하는 것을 권장합니다." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "일정 기간동안 모더레이터의 활동이 감지되지 않는 경우, 스팸 방지를 위해 이 설정은 자동으로 꺼집니다." + deliverSuspendedSoftware: "전달 정지 중인 소프트웨어" + deliverSuspendedSoftwareDescription: "취약성 등의 이유로 서버의 소프트웨어 이름 및 버전 범위를 지정하여 전달을 정지할 수 있어요. 이 버전 정보는 서버가 제공한 것이며 신뢰성은 보장되지 않아요. 버전 지정에는 semver의 범위 지정을 사용할 수 있지만, >= 2024.3.1로 지정하면 2024.3.1-custom.0과 같은 custom.0과 같은 custom 버전이 포함되지 않기 때문에 >= 2024.3.1-0과 같이 prerelease를 지정하는 것이 좋아요." + singleUserMode: "1인 모드" + singleUserMode_description: "이 서버의 이용자가 자신 뿐인 경우, 이 모드를 활성화하면 동작이 최적화됩니다." + signToActivityPubGet: "GET 요청에 사인" + signToActivityPubGet_description: "보통의 경우 활성화해 주십시오. 연합의 통신에 관한 문제가 있는 경우, 비활성화하면 개선되는 경우도 있습니다만, 서버에 따라서는 통신이 불가능해지는 경우도 있습니다." + proxyRemoteFiles: "리모트 파일 프록시" + proxyRemoteFiles_description: "활성화하면 리모트 파일을 프록시로 제공합니다. 이미지의 섬네일 생성이나 유저의 개인정보 보호에 도움을 줍니다." + allowExternalApRedirect: "ActivityPub 경유 조회에 리디렉션 허가" + allowExternalApRedirect_description: "활성화하면 다른 서버가 이 서버를 통해 제3자의 콘텐츠를 조회할 수 있습니다만, 콘텐츠의 사칭 문제가 생길 수 있습니다." + userGeneratedContentsVisibilityForVisitor: "비이용자에 대한 유저 작성 콘텐츠의 공개 범위" + userGeneratedContentsVisibilityForVisitor_description: "조정을 하기 힘든 부적절한 리모트 콘텐츠 등이 자신의 서버 경유로 의도치 않게 인터넷에 공개되는 문제의 방지 등에 도움을 줍니다." + userGeneratedContentsVisibilityForVisitor_description2: "서버에서 받은 리모트 콘텐츠를 포함해 서버 내의 모든 콘텐츠를 무조건 인터넷에 공개하는 것에는 위험이 따릅니다. 특히, 분산형 특성에 대해 모르는 열람자에게는 리모트 콘텐츠여도 서버 내에서 작성된 콘텐츠라고 잘못 인식할 수 있기에 주의가 필요합니다." + restartServerSetupWizardConfirm_title: "서버의 초기 설정 위자드를 재시도하시겠습니까?" + restartServerSetupWizardConfirm_text: "현재 일부 설정은 리셋됩니다." + entrancePageStyle: "입구 페이지의 스타일" + showTimelineForVisitor: "타임라인 표시" + showActivitiesForVisitor: "액티비티 표시하기" + _userGeneratedContentsVisibilityForVisitor: + all: "모두 공개" + localOnly: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" + none: "모두 비공개" _accountMigration: moveFrom: "다른 계정에서 이 계정으로 이사" moveFromSub: "다른 계정에 대한 별칭을 생성" @@ -1913,6 +2078,8 @@ _role: canManageCustomEmojis: "커스텀 이모지 관리" canManageAvatarDecorations: "아바타 꾸미기 관리" driveCapacity: "드라이브 용량" + maxFileSize: "업로드 가능한 최대 파일 크기" + maxFileSize_caption: "리버스 프록시나 CDN 등 전단에서 다른 설정값이 존재하는 경우가 있습니다." alwaysMarkNsfw: "파일을 항상 NSFW로 지정" canUpdateBioMedia: "아바타 및 배너 이미지 변경 허용" pinMax: "고정할 수 있는 노트 수" @@ -1927,6 +2094,7 @@ _role: descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다." canHideAds: "광고 숨기기" canSearchNotes: "노트 검색 이용 가능 여부" + canSearchUsers: "유저 검색 이용" canUseTranslator: "번역 기능의 사용" avatarDecorationLimit: "아바타 장식의 최대 붙임 개수" canImportAntennas: "안테나 가져오기 허용" @@ -1935,6 +2103,12 @@ _role: canImportMuting: "뮤트 목록 가져오기 허용" canImportUserLists: "리스트 목록 가져오기 허용" chatAvailability: "채팅을 허락" + uploadableFileTypes: "업로드 가능한 파일 유형" + uploadableFileTypes_caption: "MIME 유형을 " + uploadableFileTypes_caption2: "파일에 따라서는 유형을 검사하지 못하는 경우가 있습니다. 그러한 파일을 허가하는 경우에는 {x}를 지정으로 추가해주십시오." + noteDraftLimit: "서버측 노트 초안 작성 가능 수" + scheduledNoteLimit: "예약 게시물의 동시 작성 가능 수" + watermarkAvailable: "워터마크 기능의 사용 여부" _condition: roleAssignedTo: "수동 역할에 이미 할당됨" isLocal: "로컬 유저" @@ -2094,6 +2268,7 @@ _theme: install: "테마 설치" manage: "테마 관리" code: "테마 코드" + copyThemeCode: "테마 코드 복사" description: "설명" installed: "{name} 테마가 설치되었습니다" installedThemes: "설치된 테마" @@ -2152,7 +2327,6 @@ _theme: buttonBg: "버튼 배경" buttonHoverBg: "버튼 배경 (호버)" inputBorder: "입력 필드 테두리" - driveFolderBg: "드라이브 폴더 배경" badge: "배지" messageBg: "대화 배경" fgHighlighted: "강조된 텍스트" @@ -2194,6 +2368,7 @@ _time: minute: "분" hour: "시간" day: "일" + month: "개월" _2fa: alreadyRegistered: "이미 설정이 완료되었습니다." registerTOTP: "인증 앱 설정 시작" @@ -2323,6 +2498,7 @@ _auth: scopeUser: "다음 유저로 활동하고 있습니다." pleaseLogin: "어플리케이션의 접근을 허가하려면 로그인하십시오." byClickingYouWillBeRedirectedToThisUrl: "접근을 허용하면 자동으로 다음 URL로 이동합니다." + alreadyAuthorized: "이 애플리케이션은 이미 접근이 허가돼있습니다." _antennaSources: all: "모든 노트" homeTimeline: "팔로우중인 유저의 노트" @@ -2368,7 +2544,45 @@ _widgets: chooseList: "리스트 선택" clicker: "클리커" birthdayFollowings: "오늘이 생일인 유저" - chat: "채팅" + chat: "채팅하기" +_widgetOptions: + showHeader: "해더를 표시" + transparent: "배경을 투명하게 설정" + height: "높이" + _button: + colored: "색 입히기" + _clock: + size: "크기" + thickness: "시곗바늘의 두께" + thicknessThin: "얇게" + thicknessMedium: "보통" + thicknessThick: "굵게" + graduations: "문자반의 눈금" + graduationDots: "도트" + graduationArabic: "아라비아 숫자" + fadeGraduations: "눈금 페이드" + sAnimation: "초침 애니메이션" + sAnimationElastic: "사실적으로" + sAnimationEaseOut: "매끄럽게" + twentyFour: "24시간 표시" + labelTime: "시각" + labelTz: "시간대" + labelTimeAndTz: "시각과 시간대" + timezone: "시간대" + showMs: "밀리초 표시" + showLabel: "레이블 표시" + _jobQueue: + sound: "소리 재생" + _rss: + url: "RSS 필드의 URL" + refreshIntervalSec: "갱신 간격(초)" + maxEntries: "최대 표시 건수" + _rssTicker: + shuffle: "표시 순서 셔플" + duration: "티커 스크롤 속도(초)" + reverse: "역방향으로 스크롤" + _birthdayFollowings: + period: "기간" _cw: hide: "숨기기" show: "더 보기" @@ -2408,9 +2622,25 @@ _visibility: disableFederation: "연합에 보내지 않기" disableFederationDescription: "다른 서버로 보내지 않습니다" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "업로드되지 않은 파일이 있습니다만, 없애고 폼을 닫겠습니까?" + uploaderTip: "파일이 아직 업로드돼있지 않습니다. 파일 메뉴에서 이름 바꾸기나 이미지의 자르기, 워터마크 넣기, 압축의 유무 등을 설정할 수 있습니다. 파일은 노트 게시 시 자동으로 업로드됩니다." 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: "무슨 일이 일어나고 있나요?" @@ -2556,6 +2786,8 @@ _notification: youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다" yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다" pollEnded: "투표 결과가 발표되었습니다" + scheduledNotePosted: "예약 노트가 게시됐습니다." + scheduledNotePostFailed: "예약 노트의 게시에 실패했습니다." newNote: "새 게시물" unreadAntennaNote: "안테나 {name}" roleAssigned: "역할이 부여 되었습니다." @@ -2585,6 +2817,8 @@ _notification: quote: "인용" reaction: "리액션" pollEnded: "투표가 종료됨" + scheduledNotePosted: "예약 게시에 성공했습니다" + scheduledNotePostFailed: "예약 게시에 실패했습니다" receiveFollowRequest: "팔로우 요청을 받았을 때" followRequestAccepted: "팔로우 요청이 승인되었을 때" roleAssigned: "역할이 부여됨" @@ -2624,6 +2858,14 @@ _deck: usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다" flexible: "폭 자동 조정" enableSyncBetweenDevicesForProfiles: "프로파일 정보의 디바이스 간 동기화를 활성화" + showHowToUse: "UI 설명 보기" + _howToUse: + addColumn_title: "칼럼 추가" + addColumn_description: "칼럼의 종류를 선택해 추가할 수 있습니다." + settings_title: "UI 설정" + settings_description: "덱 UI의 상세 설정을 할 수 있습니다." + switchProfile_title: "프로필 전환" + switchProfile_description: "UI의 레이아웃을 프로필로 저장하고, 언제나 전환할 수 있습니다." _columns: main: "메인" widgets: "위젯" @@ -2635,7 +2877,7 @@ _deck: mentions: "받은 멘션" direct: "다이렉트" roleTimeline: "역할 타임라인" - chat: "채팅" + chat: "채팅하기" _dialog: charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {max}" charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}" @@ -2684,6 +2926,8 @@ _abuseReport: notifiedWebhook: "사용할 Webhook" deleteConfirm: "수신자를 삭제하시겠습니까?" _moderationLogTypes: + clearQueue: "작업 대기열 비우기" + promoteQueue: "큐의 작업을 재시도" createRole: "역할 생성" deleteRole: "역할 삭제" updateRole: "역할 수정" @@ -2741,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "업로드 날짜" attachedNotes: "첨부된 노트" + usage: "이용" thisPageCanBeSeenFromTheAuthor: "이 페이지는 파일 소유자만 열람할 수 있습니다" _externalResourceInstaller: title: "외부 사이트로부터 설치" @@ -2788,9 +3033,12 @@ _dataSaver: _avatar: title: "아이콘 이미지" description: "아이콘 이미지의 애니메이션을 멈춥니다. 애니메이션 이미지는 일반 이미지보다 파일 크기가 클 수 있으므로 데이터 사용량을 더 줄일 수 있습니다." - _urlPreview: - title: "URL 미리보기의 섬네일" - description: "URL 미리보기의 섬네일 이미지를 불러오지 않게 됩니다." + _urlPreviewThumbnail: + title: "URL 미리보기의 섬네일을 비표시" + description: "URL 미리보기의 섬네일 이미지를 불러올 수 없게 됩니다." + _disableUrlPreview: + title: "URL 미리보기 비활성화" + description: "URL 미리보기 기능을 비활성화합니다. 섬네일 이미지와 달리 링크 정보 불러오기 자체를 줄일 수 있습니다." _code: title: "문자열 강조" description: "MFM 등으로 문자열 강조 기법을 사용할 때 누르기 전에는 불러오지 않습니다. 문자열 강조에서는 강조할 언어마다 그 정의 파일을 불러와야 하지만 이를 자동으로 불러오지 않으므로 데이터 사용량을 줄일 수 있습니다." @@ -2848,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "URL 미리보기 설정" enable: "URL 미리보기 활성화" + allowRedirect: "미리보기 위치의 리디렉션 허가" + allowRedirectDescription: "입력된 URL이 리디렉션될 경우, 그 리디렉션 위치를 따라 미리보기를 표시할 것인지 설정합니다. 비활성화하면 서버 리소스를 절약할 수 있습니다만, 리디렉션 위치의 내용은 표시되지 않습니다." timeout: "미리보기를 불러올 때의 타임아웃 (ms)" timeoutDescription: "미리보기를 로딩하는데 걸리는 시간이 정한 시간보다 오래 걸리는 경우, 미리보기를 생성하지 않습니다." maximumContentLength: "Content-Length의 최대치 (byte)" @@ -2921,10 +3171,6 @@ _customEmojisManager: uploadSettingDescription: "여기서 이모지를 업로드 할 때의 동작을 설정할 수 있습니다." directoryToCategoryLabel: "디렉토리 이름을 \"category\"로 입력하기" directoryToCategoryCaption: "디렉토리를 드래그 앤 드롭한 경우, 디렉토리 이름을 \"category\"로 입력합니다." - emojiInputAreaCaption: "이모지를 등록할 방법을 선택해주세요." - emojiInputAreaList1: "이 틀 안에 이미지 파일 또는 디렉토리를 끌어서 가져오기" - emojiInputAreaList2: "이 링크를 클릭해서 PC에서 선택하기" - emojiInputAreaList3: "이 링크를 클릭해서 드라이브에서 선택하기" confirmRegisterEmojisDescription: "리스트에 표시되어진 이모지를 새로운 커스텀 이모지로 등록합니다. 실행할까요? (부하를 피하기 위해, 한 번에 등록할 수 있는 이모지는 {count}건까지 입니다.)" confirmClearEmojisDescription: "편집 내용을 지우고, 목록에 표시되어진 이모지를 지웁니다. 실행할까요?" confirmUploadEmojisDescription: "드래그 앤 드롭한 {count}개의 파일을 드라이브에 업로드 합니다. 실행할까요?" @@ -2992,6 +3238,7 @@ _bootErrors: otherOption1: "클라이언트 설정 및 캐시 삭제" otherOption2: "간편 클라이언트 실행" otherOption3: "복구 툴 실행" + otherOption4: "Misskey를 세이프 모드로 열기" _search: searchScopeAll: "전체" searchScopeLocal: "로컬" @@ -3000,3 +3247,196 @@ _search: pleaseEnterServerHost: "서버의 호스트를 입력해 주세요." pleaseSelectUser: "유저를 선택해주세요" serverHostPlaceholder: "예: misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey의 설치가 완료됐습니다!" + firstCreateAccount: "먼저 관리자 계정을 만듭시다." + accountCreated: "관리자 계정이 만들어졌습니다!" + serverSetting: "서버 설정" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "이 위자드로 쉽게 최적화된 서버의 설정을 할 수 있습니다." + settingsYouMakeHereCanBeChangedLater: "이 설정은 나중에 변경 가능합니다." + howWillYouUseMisskey: "Misskey를 어떻게 사용하십니까?" + _use: + single: "1인 서버" + single_description: "자신 전용 서버로 혼자서 사용" + single_youCanCreateMultipleAccounts: "1인 서버로 운영하는 경우에도 계정은 필요에 따라 여러 개 만들 수 있습니다." + group: "그룹 서버" + group_description: "신뢰 가능한 다른 유저를 초대해 여러 명이 사용" + open: "오픈 서버" + open_description: "불특정 다수의 유저를 받아들이는 운영을 함" + openServerAdvice: "불특정 다수의 유저를 받아들이는 것에는 위험이 따릅니다. 문제에 대처할 수 있도록 확실한 조정 체제로 운영하는 것을 권장합니다." + openServerAntiSpamAdvice: "자신의 서버가 스팸으로 사용되지 않게끔 reCAPTCHA라는 안티 봇 기능을 활성화하는 등 보안에 대해서도 세심한 주의가 필요합니다." + howManyUsersDoYouExpect: "어느 정도의 인원으로 생각 중이십니까?" + _scale: + small: "100명 이하(소규모)" + medium: "100명 이상 1000명 이하(중간 규모)" + large: "1000명 이상(대규모)" + largeScaleServerAdvice: "대규모 서버에서는 부하분산이나 데이터베이스의 복제 등 높은 인프라스트럭처 지식이 필요할 수 있습니다." + doYouConnectToFediverse: "Fediverse에 접속하시겠습니까?" + doYouConnectToFediverse_description1: "분산형 서버로 구성된 네트워크(Fediverse)에 접속하면 다른 서버와 서로 콘텐츠의 주고받기를 할 수 있습니다." + doYouConnectToFediverse_description2: "Fediverse에 접속하는 것을 '연합'이라고도 부릅니다." + youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다." + remoteContentsCleaning: "리모트 콘텐츠 자동 정리" + remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다." + adminInfo: "관리자 정보" + adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다." + adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다." + followingSettingsAreRecommended: "아래의 설정이 권장됩니다." + applyTheseSettings: "이 설정을 적용" + skipSettings: "설정 건너뛰기" + settingsCompleted: "설정이 완료됐습니다!" + settingsCompleted_description: "수고하셨습니다. 준비를 마쳤으므로 바로 서버의 이용을 시작하실 수 있습니다." + settingsCompleted_description2: "상세한 서버 설정은 '제어판'에서 하실 수 있습니다." + donationRequest: "기부 요청" + _donationRequest: + text1: "Misskey는 자원봉사자들에 의해 개발되는 무료 소프트웨어입니다." + text2: "앞으로도 계속해서 개발을 할 수 있도록 괜찮으시다면 부디 기부를 부탁드립니다." + text3: "지원자 대상 특전도 있습니다!" +_uploader: + editImage: "이미지 편집" + compressedToX: "{x}로 압축" + savedXPercent: "{x}% 절약" + abortConfirm: "업로드되지 않은 파일이 있습니다만, 그만 두시겠습니까?" + doneConfirm: "업로드되지 않은 파일이 있습니다만, 완료하시겠습니까?" + maxFileSizeIsX: "업로드 가능한 최대 파일 크기는 {x}입니다." + allowedTypes: "업로드 가능한 파일 유형" + tip: "파일은 아직 업로드되지 않았습니다. 이 다이얼로그에서 업로드 전의 확인, 이름 바꾸기, 압축, 자르기 등을 하실 수 있습니다. 준비가 되셨다면 '업로드' 버튼을 클릭해 업로드를 시작하실 수 있습니다." +_clientPerformanceIssueTip: + title: "배터리 소비가 심하다고 생각되시면" + makeSureDisabledAdBlocker: "광고 차단을 비활성화해 주십시오." + makeSureDisabledAdBlocker_description: "광고 차단은 성능에 영향을 미칠 수 있습니다. OS의 기능이나 브라우저의 기능, 애드온 등으로 광고 차단이 활성화돼있지 않은지 확인해 주십시오." + makeSureDisabledCustomCss: "커스텀 CSS를 무효로 해주십시오." + makeSureDisabledCustomCss_description: "스타일을 덮어쓰기하면 성능에 영향을 미칠 수 있습니다. 커스텀 CSS나 스타일을 덮어쓰기하는 확장 기능이 유효로 돼있는지 확인해주십시오." + makeSureDisabledAddons: "확장 기능을 비활성화해 주십시오." + makeSureDisabledAddons_description: "일부 확장 기능은 클라이언트의 동작에 간섭해 성능에 영향을 미칠 수 있습니다. 브라우저의 확장 기능을 비활성화해 개선할지 확인해주십시오." +_clip: + tip: "클립은 노트를 정리할 수 있는 기능입니다." +_userLists: + tip: "임의의 유저가 포함된 리스트를 작성할 수 있습니다. 작성한 리스트는 타임라인으로 표시가 가능합니다." +watermark: "워터마크" +defaultPreset: "기본 프리셋" +_watermarkEditor: + tip: "이미지에 크레딧 정보 등의 워터마크를 추가할 수 있습니다." + quitWithoutSaveConfirm: "보존하지 않고 종료하시겠습니까?" + driveFileTypeWarn: "이 파이" + driveFileTypeWarnDescription: "이미지 파일을 선택해주십시오." + title: "워터마크 편집" + cover: "전체에 붙이기" + repeat: "전면에 깔기" + preserveBoundingRect: "회전 시 빠져나오지 않도록 조정" + opacity: "불투명도" + scale: "크기" + text: "텍스트" + qr: "QR 코드" + position: "위치" + margin: "여백" + type: "종류" + image: "이미지" + advanced: "고급" + angle: "각도" + stripe: "줄무늬" + stripeWidth: "라인의 폭" + stripeFrequency: "라인의 수" + polkadot: "물방울 무늬" + checker: "체크 무늬" + polkadotMainDotOpacity: "주요 물방울의 불투명도" + polkadotMainDotRadius: "주요 물방울의 크기" + polkadotSubDotOpacity: "서브 물방울의 불투명도" + polkadotSubDotRadius: "서브 물방울의 크기" + polkadotSubDotDivisions: "서브 물방울의 수" + leaveBlankToAccountUrl: "빈칸일 경우 계정의 URL로 됩니다." + failedToLoadImage: "이미지 로딩에 실패했습니다." +_imageEffector: + title: "이펙트" + addEffect: "이펙트를 추가" + discardChangesConfirm: "변경을 취소하고 종료하시겠습니까?" + failedToLoadImage: "이미지 로딩에 실패했습니다." + _fxs: + chromaticAberration: "색수차" + glitch: "글리치" + mirror: "미러" + invert: "색 반전" + grayscale: "흑백" + blur: "흐림 효과" + pixelate: "모자이크" + colorAdjust: "색조 보정" + colorClamp: "색 압축" + colorClampAdvanced: "색 압축(고급)" + distort: "뒤틀림" + threshold: "이진화" + zoomLines: "집중선" + stripe: "줄무늬" + polkadot: "물방울 무늬" + checker: "체크 무늬" + blockNoise: "노이즈 방지" + tearing: "티어링" + fill: "채우기" + _fxProps: + angle: "각도" + scale: "크기" + size: "크기" + radius: "반지름" + samples: "샘플 수" + offset: "위치" + color: "색" + opacity: "불투명도" + normalize: "노멀라이즈" + amount: "양" + lightness: "밝음" + contrast: "대비" + hue: "색조" + brightness: "밝기" + saturation: "채도" + max: "최대 값" + min: "최소 값" + direction: "방향" + phase: "위상" + frequency: "빈도" + strength: "강도" + glitchChannelShift: "글리치" + seed: "시드 값" + redComponent: "빨간색 요소" + greenComponent: "녹색 요소" + blueComponent: "파란색 요소" + threshold: "한계 값" + centerX: "X축 중심" + centerY: "Y축 중심" + zoomLinesSmoothing: "다듬기" + zoomLinesSmoothingDescription: "다듬기와 집중선 폭 설정은 같이 쓸 수 없습니다." + zoomLinesThreshold: "집중선 폭" + zoomLinesMaskSize: "중앙 값" + zoomLinesBlack: "검은색으로 하기" + circle: "원형" +drafts: "초안" +_drafts: + select: "초안 선택" + cannotCreateDraftAnymore: "초안 작성 가능 수를 초과했습니다." + cannotCreateDraft: "이 내용으로는 초안을 작성할 수 없습니다. " + delete: "초안 삭제\n" + deleteAreYouSure: "초안을 삭제하시겠습니까?" + noDrafts: "초안 없음\n" + replyTo: "{user}에 회신" + quoteOf: "{user} 노트에 인용" + postTo: "{channel}에 게시" + saveToDraft: "초안에 저장" + restoreFromDraft: "초안에서 복원\n" + restore: "복원" + listDrafts: "초안 목록" + schedule: "게시 예약" + listScheduledNotes: "예약 게시물 목록" + cancelSchedule: "예약 해제" +qr: "QR 코드" +_qr: + showTabTitle: "보기" + readTabTitle: "읽어들이기" + shareTitle: "{name} {acct}" + shareText: "Fediverse로 저를 팔로우해 주세요!" + chooseCamera: "카메라 선택" + cannotToggleFlash: "플래시 선택 불가" + turnOnFlash: "플래시 켜기" + turnOffFlash: "플래시 끄기" + startQr: "코드 리더 재개" + stopQr: "코드 리더 정지" + noQrCodeFound: "QR 코드를 찾을 수 없습니다." + scanFile: "단말기의 이미지 스캔" + raw: "텍스트" + mfm: "MFM" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 455a71f302..7017d81733 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -393,6 +393,9 @@ file: "ໄຟລ໌" replies: "ຕອບ​ກັບ" renotes: "Renote" information: "ກ່ຽວກັບ" +_imageEditing: + _vars: + filename: "ຊື່ໄຟລ໌" _chat: invitations: "ເຊີນ" noHistory: "​ບໍ່​ມີປະຫວັດ" @@ -428,11 +431,17 @@ _widgets: jobQueue: "ຄິວວຽກ" _userList: chooseList: "ເລືອກບັນຊີລາຍການ" +_widgetOptions: + height: "ຄວາມສູງ" _cw: show: "ໂຫຼດເພີ່ມເຕີມ" _visibility: home: "ໜ້າຫຼັກ" followers: "ຜູ້ຕິດຕາມ" + specified: "ໂພສ Direct note" +_postForm: + _howToUse: + menu_title: "ເມນູ" _profile: name: "ຊື່" username: "ຊື່ຜູ້ໃຊ້" @@ -470,6 +479,7 @@ _deck: list: "ລາຍການ" channel: "ຊ່ອງ" mentions: "ກ່າວເຖິງເຈົ້າ" + direct: "ໂພສ Direct note" _webhookSettings: name: "ຊື່" _abuseReport: @@ -483,3 +493,5 @@ _remoteLookupErrors: title: "ບໍ່ພົບ" _search: searchScopeAll: "ທັງໝົດ" +_watermarkEditor: + image: "ຮູບພາບ" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index bc8c86f2c3..32e021d1a1 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -118,6 +118,8 @@ renotedToX: "Renoted naar {name}" cantRenote: "Dit bericht kan niet worden herdeeld" cantReRenote: "Een herdeling kan niet worden herdeeld" quote: "Quote" +inChannelRenote: "Alleen-kanaal Renote" +inChannelQuote: "Alleen-kanaal Citaat" renoteToChannel: "Renote naar kanaal" renoteToOtherChannel: "Renote naar ander kanaal" pinnedNote: "Vastgemaakte notitie" @@ -143,6 +145,8 @@ unmarkAsSensitive: "Geen NSFW" enterFileName: "Invoeren bestandsnaam" mute: "Dempen" unmute: "Stop dempen" +renoteMute: "Renotes dempen" +renoteUnmute: "Dempen Renotes opheffen" block: "Blokkeren" unblock: "Deblokkeren" suspend: "Opschorten" @@ -152,7 +156,10 @@ unblockConfirm: "Ben je zeker dat je deze account wil blokkeren?" suspendConfirm: "Ben je zeker dat je deze account wil suspenderen?" unsuspendConfirm: "Ben je zeker dat je deze account wil opnieuw aanstellen?" selectList: "Kies een lijst." +editList: "Lijst bewerken" +selectChannel: "Kanaal selecteren" selectAntenna: "Kies een antenne" +editAntenna: "Antenne bewerken" createAntenna: "Antenne aanmaken" selectWidget: "Kies een widget" editWidgets: "Bewerk widgets" @@ -166,6 +173,9 @@ addEmoji: "Toevoegen emoji" settingGuide: "Aanbevolen instellingen" cacheRemoteFiles: "Externe bestanden cachen" cacheRemoteFilesDescription: "Als deze instelling uitgeschakeld is worden bestanden altijd direct van remote servers geladen. Hiermee wordt opslagruimte bespaard, maar doordat er geen thumbnails worden gegenereerd, zal netwerkverkeer toenemen." +youCanCleanRemoteFilesCache: "Klik op de 🗑️ knop in de bestandsbeheerweergave om de cache te wissen." +cacheRemoteSensitiveFiles: "Gevoelige bestanden van externe instances in de cache bewaren" +cacheRemoteSensitiveFilesDescription: "Als deze instelling is uitgeschakeld, worden gevoelige bestanden op afstand direct vanuit de instantie op afstand geladen zonder caching." flagAsBot: "Markeer dit account als een robot." flagAsBotDescription: "Als dit account van een programma wordt beheerd, zet deze vlag aan. Het aanzetten helpt andere ontwikkelaars om bijvoorbeeld onbedoelde feedback loops te doorbreken of om Misskey meer geschikt te maken." flagAsCat: "Markeer dit account als een kat." @@ -174,6 +184,7 @@ flagShowTimelineReplies: "Toon antwoorden op de tijdlijn." flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook antwoorden op andere en niet alleen jouw eigen notities." autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker al volgt." addAccount: "Account toevoegen" +reloadAccountsList: "Accountlijst opnieuw laden" loginFailed: "Aanmelding mislukt." showOnRemote: "Toon op de externe instantie." continueOnRemote: "Verder op remote server" @@ -205,6 +216,7 @@ perHour: "Per uur" perDay: "Per dag" stopActivityDelivery: "Stop met versturen activiteiten" blockThisInstance: "Blokkeer deze server" +silenceThisInstance: "Instantie dempen" mediaSilenceThisInstance: "Media van deze server dempen" operations: "Verwerkingen" software: "Software" @@ -225,6 +237,7 @@ clearCachedFiles: "Cache opschonen" clearCachedFilesConfirm: "Weet je zeker dat je alle externe bestanden in de cache wilt verwijderen?" blockedInstances: "Geblokkeerde servers" blockedInstancesDescription: "Maak een lijst van de servers die moeten worden geblokkeerd, gescheiden door regeleinden. Geblokkeerde servers kunnen niet meer communiceren met deze server." +silencedInstances: "Gedempte instanties" silencedInstancesDescription: "Geef de hostnamen van de servers die je wil dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, kunnen alleen maar volgverzoeken maken, en kunnen lokale accounts niet vermelden als ze niet gevolgd worden. Geblokkeerde servers worden hier niet door beïnvloed." mediaSilencedInstances: "Media-gedempte servers" mediaSilencedInstancesDescription: "Geef de hostnamen van de servers die je wil media-dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, en kunnen geen eigen emojis gebruiken. Geblokkeerde servers worden hier niet door beïnvloed." @@ -237,7 +250,6 @@ noUsers: "Er zijn geen gebruikers." editProfile: "Bewerk Profiel" noteDeleteConfirm: "Ben je zeker dat je dit bericht wil verwijderen?" pinLimitExceeded: "Je kunt geen berichten meer vastprikken" -intro: "Installatie van Misskey geëindigd! Maak nu een beheerder aan." done: "Klaar" processing: "Bezig met verwerken" preview: "Voorbeeld" @@ -291,6 +303,10 @@ noMoreHistory: "Er is geen verdere geschiedenis" startChat: "Chat starten" nUsersRead: "gelezen door {n}" agreeTo: "Ik stem in met {0}" +agree: "Akkoord" +agreeBelow: "Ik ga akkoord met de volgende" +basicNotesBeforeCreateAccount: "Belangrijke informatie" +termsOfService: "Gebruiksvoorwaarden" start: "Aan de slag" home: "Startpagina" remoteUserCaution: "Aangezien deze gebruiker van een externe server afkomstig is, kan de weergegeven informatie onvolledig zijn." @@ -336,6 +352,7 @@ copyUrl: "URL kopiëren" rename: "Hernoemen" avatar: "Avatar" banner: "Banner" +displayOfSensitiveMedia: "Weergave van gevoelige media" whenServerDisconnected: "Wanneer de verbinding met de server wordt onderbroken" disconnectedFromServer: "Verbinding met de server onderbroken." reload: "Verversen" @@ -373,7 +390,10 @@ bannerUrl: "Banner URL" backgroundImageUrl: "URL afbeelding" basicInfo: "Basisinformatie" pinnedUsers: "Vastgeprikte gebruikers" +pinnedUsersDescription: "Een lijst met gebruikersnamen, gescheiden door regeleinden, die moet worden vastgemaakt in het tabblad “Verkennen”" pinnedPages: "Vastgeprikte pagina's" +pinnedPagesDescription: "Voer de paden in van de Pagina's die je aan de bovenste pagina van deze instantie wilt vastmaken, gescheiden door regeleinden." +pinnedClipId: "ID van de clip die moet worden vastgepind" pinnedNotes: "Vastgemaakte notitie" hcaptcha: "hCaptcha" enableHcaptcha: "Inschakelen hCaptcha" @@ -392,6 +412,7 @@ turnstile: "Tourniquet" enableTurnstile: "Inschakelen tourniquet" turnstileSiteKey: "Site sleutel" turnstileSecretKey: "Geheime sleutel" +avoidMultiCaptchaConfirm: "Het gebruik van meerdere Captcha-systemen kan interferentie tussen deze systemen veroorzaken. Wil je de andere Captcha-systemen die momenteel actief zijn uitschakelen? Als je wilt dat ze ingeschakeld blijven, druk dan op annuleren." antennas: "Antennes" manageAntennas: "Antennes beheren" name: "Naam" @@ -399,6 +420,13 @@ antennaSource: "Bron antenne" antennaKeywords: "Sleutelwoorden" antennaExcludeKeywords: "Blokkeerwoorden" antennaExcludeBots: "Bot-accounts uitsluiten" +antennaKeywordsDescription: "Scheid met spaties voor een EN-voorwaarde of met regeleinden voor een OF-voorwaarde." +notifyAntenna: "Houd een notificatie bij nieuwe notities" +withFileAntenna: "Alleen notities met bestanden" +excludeNotesInSensitiveChannel: "Sluit notities uit van gevoelige kanalen" +enableServiceworker: "Activeer pushmeldingen in de browser" +antennaUsersDescription: "Lijst één gebruikersnaam per regel" +caseSensitive: "Hoofdlettergevoelig" withReplies: "Antwoorden toevoegen" connectedTo: "De volgende accounts zijn verbonden" notesAndReplies: "Berichten en reacties" @@ -419,18 +447,30 @@ about: "Over" aboutMisskey: "Over Misskey" administrator: "Beheerder" token: "Token" +2fa: "Twee factor authenticatie" +setupOf2fa: "Tweefactorauthenticatie instellen" +totp: "Verificatie-App" +totpDescription: "Log in via de verificatie-app met het eenmalige wachtwoord" moderator: "Moderator" moderation: "Moderatie" +moderationNote: "Moderatienotitie" +moderationNoteDescription: "Voer hier notities in. Deze zijn alleen zichtbaar voor de moderators." +addModerationNote: "Moderatienotitie toevoegen" +moderationLogs: "Moderatieprotocollen" nUsersMentioned: "Vermeld door {n} gebruikers" +securityKeyAndPasskey: "Beveiligings- en pasjessleutels" securityKey: "Beveiligingssleutel" lastUsed: "Laatst gebruikt" +lastUsedAt: "Laatst gebruikt: {t}" unregister: "Uitschrijven" passwordLessLogin: "Inloggen zonder wachtwoord" +passwordLessLoginDescription: "Maakt aanmelden zonder wachtwoord mogelijk met een beveiligingstoken of -wachtsleutel" resetPassword: "Wachtwoord terugzetten" newPasswordIs: "Het nieuwe wachtwoord is „{password}”." reduceUiAnimation: "Verminder beweging in de UI" share: "Delen" notFound: "Niet gevonden" +notFoundDescription: "Er is geen pagina gevonden onder deze URL." uploadFolder: "Standaardmap voor uploaden" markAsReadAllNotifications: "Markeer alle meldingen als gelezen" markAsReadAllUnreadNotes: "Markeer alle berichten als gelezen" @@ -449,13 +489,53 @@ retype: "Opnieuw invoeren" noteOf: "Notitie van {user}" quoteAttached: "Citaat" quoteQuestion: "Toevoegen als citaat?" +attachAsFileQuestion: "De tekst op het klembord is te lang. Wilt u het als een tekstbestand bijvoegen?" +onlyOneFileCanBeAttached: "Per bericht kan slechts één bestand worden bijgevoegd" +signinRequired: "Gelieve te registreren of in te loggen om verder te gaan" signinOrContinueOnRemote: "Ga naar je eigen instantie of registreer je/log in op deze server om door te gaan." invitations: "Uitnodigen" +invitationCode: "Uitnodigingscode" +checking: "Wordt gecheckt ..." +available: "Beschikbaar" +unavailable: "Onbeschikbaar" +usernameInvalidFormat: "Je kunt kleine letters, hoofdletters, cijfers en onderstrepingstekens gebruiken." +tooShort: "Te kort" +tooLong: "Te lang" +weakPassword: "Zwak wachtwoord" +normalPassword: "Redelijke wachtwoord" +strongPassword: "Sterk wachtwoord" +passwordMatched: "Lucifers" +passwordNotMatched: "Komt niet overeen" +signinWith: "Aanmelden met {x}" +signinFailed: "Inloggen mislukt. Controleer gebruikersnaam en wachtwoord." +or: "Of" +language: "Taal" +uiLanguage: "Taal van gebruikersinterface" +aboutX: "Over {x}" +emojiStyle: "Emoji-stijl" +native: "Inheems" menuStyle: "Menustijl" style: "Stijl" drawer: "Lade" popup: "Pop-up" +showNoteActionsOnlyHover: "Toon notitiemenu alleen bij muisaanwijzer" showReactionsCount: "Zie het aantal reacties op notities" +noHistory: "Geen geschiedenis gevonden" +signinHistory: "Inloggeschiedenis" +enableAdvancedMfm: "Uitgebreide MFM activeren" +enableAnimatedMfm: "Geanimeerde MFM activeren" +doing: "In uitvoering..." +category: "Categorie" +tags: "Aliassen" +docSource: "Broncode van dit document" +createAccount: "Gebruikersaccount maken" +existingAccount: "Bestaand gebruikersaccount" +regenerate: "Regenereer" +fontSize: "Lettergrootte" +mediaListWithOneImageAppearance: "Hoogte van medialijsten met slechts één afbeelding" +limitTo: "Beperken tot {x}" +noFollowRequests: "Je hebt geen lopende volgverzoeken" +openImageInNewTab: "Afbeeldingen in nieuw tabblad openen" dashboard: "Overzicht" local: "Lokaal" remote: "Remote" @@ -470,38 +550,387 @@ promote: "Promoot" numberOfDays: "Aantal dagen" hideThisNote: "Verberg deze notitie" showFeaturedNotesInTimeline: "Laat featured notities in tijdlijn zien" +objectStorage: "Object Storage" +useObjectStorage: "Object Storage gebruiken" +objectStorageBaseUrl: "Basis-URL" +objectStorageBaseUrlDesc: "De URL die wordt gebruikt als referentie. Als je een CDN of proxy gebruikt, voer dan de URL daarvan in. Gebruik voor S3 ‘https://.s3.amazonaws.com’. Gebruik voor GCS of vergelijkbaar ‘https://storage.googleapis.com/’." +objectStorageBucket: "Bucket" +objectStorageBucketDesc: "Geef de bucketnaam op die bij je provider wordt gebruikt." +objectStoragePrefix: "Prefix" +objectStoragePrefixDesc: "Bestanden worden opgeslagen in de mappen onder deze prefix." +objectStorageEndpoint: "Endpoint" +objectStorageEndpointDesc: "Laat dit leeg als je AWS S3 gebruikt, anders geef je het eindpunt op als ‘’ of ‘:’, afhankelijk van de service die je gebruikt." +objectStorageRegion: "Region" +objectStorageRegionDesc: "Voer een regio in zoals “xx-east-1”. Als je provider geen onderscheid maakt tussen regio's, voer dan “us-east-1” in. Laat leeg als je AWS-configuratiebestanden of omgevingsvariabelen gebruikt." +objectStorageUseSSL: "SSL gebruiken" +objectStorageUseSSLDesc: "Deactiveer dit als u geen HTTPS gebruikt voor API-verbindingen" +objectStorageUseProxy: "Verbinden via proxy" +objectStorageUseProxyDesc: "Deactiveer dit als u geen proxy wilt gebruiken voor verbindingen met de API" +objectStorageSetPublicRead: "Instellen op “public-read” op upload" +s3ForcePathStyleDesc: "Als s3ForcePathStyle is geactiveerd, moet de bucketnaam niet worden opgegeven in de hostnaam van de URL, maar in het pad van de URL. Deze optie moet mogelijk worden geactiveerd als services zoals een zelfbediende Minio-instantie worden gebruikt." +serverLogs: "Serverprotocollen" +deleteAll: "Alles verwijderen" +showFixedPostForm: "Het postingformulier bovenaan de tijdbalk weergeven" +showFixedPostFormInChannel: "Het postingformulier bovenaan de tijdbalk weergeven (Kanalen)" +withRepliesByDefaultForNewlyFollowed: "Toon replies van nieuw gevolgde gebruikers standaard in de tijdlijn" +newNoteRecived: "Er zijn nieuwe notities" +sounds: "Geluiden" sound: "Geluid" +listen: "Luisteren" +none: "Niets" +showInPage: "Weergeven in een pagina" +popout: "Pop-Up" +volume: "Volume" +masterVolume: "Hoofdvolume" notUseSound: "Geluid uitschakelen" useSoundOnlyWhenActive: "Geluid alleen inschakelen wanneer Misskey actief is" +details: "Details" +renoteDetails: "Renote Details" +chooseEmoji: "Emoji selecteren" +unableToProcess: "De operatie kan niet worden voltooid." +recentUsed: "Recent gebruikt" +install: "Installeren" +uninstall: "Deinstalleren" +installedApps: "Geautoriseerde toepassingen" +nothing: "Niets te zien hier" +installedDate: "Geautoriseerd at" +lastUsedDate: "Laatst gebruikt at" +state: "Status" +sort: "Sorteren" +ascendingOrder: "Oplopende volgorde" +descendingOrder: "Aflopende volgorde" +scratchpad: "Testomgeving" +scratchpadDescription: "De testomgeving biedt een gebied voor AiScript experimenten. Daar kunt u AiScript schrijven en uitvoeren en de effecten ervan op Misskey controleren." uiInspector: "UI-inspecteur" +uiInspectorDescription: "De lijst met servers van UI-componenten kan worden bekeken in de cache. De UI-component wordt gegenereerd door de functie Ui:C:" +output: "Uitvoer" +script: "Script" +disablePagesScript: "AiScript uitschakelen op pagina's" +updateRemoteUser: "Gebruikersinformatie bijwerken" unsetUserAvatar: "Avatar verwijderen" unsetUserAvatarConfirm: "Weet je zeker dat je je avatar wil verwijderen?" unsetUserBanner: "Banner verwijderen" unsetUserBannerConfirm: "Weet je zeker dat je je banner wil verwijderen?" +deleteAllFiles: "Alle bestanden verwijderen" +deleteAllFilesConfirm: "Wil je echt alle bestanden verwijderen?" +removeAllFollowing: "Ontvolg alle gevolgde gebruikers" +removeAllFollowingDescription: "Door dit uit te voeren worden alle accounts van {host} ontvolgd. Voer dit uit als de instantie bijvoorbeeld niet meer bestaat." +userSuspended: "Deze gebruiker is geschorst." +userSilenced: "Deze gebruiker is instantiebreed gedempt." +yourAccountSuspendedTitle: "Deze account is geschorst" +yourAccountSuspendedDescription: "Dit gebruikersaccount is geschorst omdat het de gebruiksvoorwaarden van deze server heeft geschonden. Neem contact op met de operator voor meer informatie. Maak geen nieuwe gebruikersaccount aan." +tokenRevoked: "Ongeldig token" +tokenRevokedDescription: "Het token is verlopen. Log opnieuw in." +accountDeleted: "Het gebruikersaccount is verwijderd" +accountDeletedDescription: "Deze account is verwijderd." +menu: "Menu" +divider: "Scheider" +addItem: "Element toevoegen" +rearrange: "Sorteren" +relays: "Relays" +addRelay: "Relay toevoegen" +inboxUrl: "Inbox-URL" +addedRelays: "Toegevoegd Relays" +serviceworkerInfo: "Moet worden geactiveerd voor pushmeldingen." +deletedNote: "Verwijderde notitie" +invisibleNote: "Privé notitie" +enableInfiniteScroll: "Automatisch meer laden" +visibility: "Zichtbaarheid" +poll: "Peiling" +useCw: "Inhoudswaarschuwing gebruiken" +enablePlayer: "Videospeler openen" +disablePlayer: "Videospeler sluiten" expandTweet: "Notitie uitklappen" +themeEditor: "Thema-editor" +description: "Beschrijving" +describeFile: "Beschrijving toevoegen" +enterFileDescription: "Beschrijving invoeren" +author: "Auteur" +leaveConfirm: "Er zijn niet-opgeslagen wijzigingen. Wil je ze verwijderen?" +manage: "Beheer" +plugins: "Plugins" +preferencesBackups: "Instellingen Back-ups" +deck: "Dek" +undeck: "Dek verlaten" +useBlurEffectForModal: "Vervagingseffect gebruiken voor modals" +useFullReactionPicker: "Volledige reaktieselectier gebruiken" +width: "Breedte" +height: "Hoogte" +large: "Groot" +medium: "Medium" +small: "Klein" +generateAccessToken: "Toegangstoken genereren" +permission: "Machtigingen" adminPermission: "Administratorrechten" +enableAll: "Alle activeren" +disableAll: "Alle deactiveren" +tokenRequested: "Toegang verlenen tot het gebruikersaccount" +pluginTokenRequestedDescription: "Deze plugin kan de hier geconfigureerde autorisaties gebruiken." +notificationType: "Type melding" +edit: "Bewerken" +emailServer: "Email-Server" +enableEmail: "Email distributie inschakelen" +emailConfigInfo: "Wordt gebruikt om je email te bevestigen tijdens het aanmelden of als je je wachtwoord bent vergeten" +email: "Email" +emailAddress: "Email adres" +smtpConfig: "SMTP-server configuratie" smtpHost: "Server" +smtpPort: "Poort" smtpUser: "Gebruikersnaam" smtpPass: "Wachtwoord" +emptyToDisableSmtpAuth: "Laat gebruikersnaam en wachtwoord leeg om SMTP-authenticatie uit te schakelen." +smtpSecure: "Impliciet SSL/TLS gebruiken voor SMTP-verbindingen" +smtpSecureInfo: "Schakel dit uit bij gebruik van STARTTLS" +testEmail: "Emailversand testen" +wordMute: "Woord dempen" wordMuteDescription: "Minimaliseert notities die het gespecificeerde woord of zin bevatten. Geminimaliseerde notities kunnen worden weergegeven door er op te klikken." hardWordMute: "Harde woorddemping" showMutedWord: "Gedempte woorden weergeven" hardWordMuteDescription: "Verbert notities die het gespecificeerde woord of zin bevatten. In tegenstelling tot woorddemping wordt de notitie volledig verborgen." +regexpError: "Fout in reguliere expressie" +regexpErrorDescription: "Er is een fout opgetreden in de reguliere expressie op regel {line} van uw {tab} woord dempen:" +instanceMute: "Instantie dempers" +userSaysSomething: "{name} zei iets" userSaysSomethingAbout: "{name} zei iets over '{word}'" +makeActive: "Activeren" +display: "Weergave" +copy: "Kopiëren" copiedToClipboard: "Naar het klembord gekopieerd" +metrics: "Metrieken" +overview: "Overzicht" +logs: "Protocollen" +delayed: "Vertraagd" +database: "Database" +channel: "Kanalen" +create: "Creëer" +notificationSetting: "Instellingen meldingen" +notificationSettingDesc: "Selecteer het type meldingen dat moet worden weergegeven." +useGlobalSetting: "Globale instelling gebruiken" +useGlobalSettingDesc: "Als deze optie is ingeschakeld, worden de meldingsinstellingen van je account gebruikt. Als deze optie uitgeschakeld is, kunnen individuele configuraties worden gemaakt." +other: "Ander" +regenerateLoginToken: "Login token opnieuw genereren" +regenerateLoginTokenDescription: "Regenereren van het token dat intern wordt gebruikt om in te loggen. Dit is normaal gezien niet nodig. Alle apparaten worden afgemeld tijdens het regenereren." theKeywordWhenSearchingForCustomEmoji: "Dit is het keyword dat gebruikt wordt bij het zoeken naar eigen emojis." +setMultipleBySeparatingWithSpace: "Scheid elementen met een spatie om meerdere instellingen te configureren." +fileIdOrUrl: "Bestands-ID of URL" +behavior: "Gedrag" +sample: "Voorbeeld" +abuseReports: "Meldt" +reportAbuse: "Meld" +reportAbuseRenote: "Meld renote" +reportAbuseOf: "Meld {name}" fillAbuseReportDescription: "Vul s.v.p. de details in over deze melding. Geef, als het over een specifieke notitie gaat, ook de URL op." +abuseReported: "Uw rapport is verzonden. Hartelijk dank." +reporter: "Verslaggever" +reporteeOrigin: "Oorsprong van de gemelde persoon" +reporterOrigin: "Verslaggever Oorsprong" +send: "Stuur" +openInNewTab: "In nieuw tabblad openen" +openInSideView: "In zijaanzicht openen" +defaultNavigationBehaviour: "Standaard navigatie gedrag" +editTheseSettingsMayBreakAccount: "Het wijzigen van deze instellingen kan je account beschadigen." +instanceTicker: "Instantie-informatie van notities" +waitingFor: "Wachten op {x}" +random: "Willekeurig" +system: "Systeem" +switchUi: "UI omschakelen" +desktop: "Desktop" +clip: "Clip aanmaken" +createNew: "Nieuwe aanmaken" +optional: "Optioneel" +createNewClip: "Nieuwe clip aanmaken" +unclip: "Van clip verwijderen" +confirmToUnclipAlreadyClippedNote: "Deze notitie is al toegevoegd aan de clip “{name}”. Wil je deze uit deze clip verwijderen?" +public: "Openbare" +private: "Privé" +i18nInfo: "Misskey wordt in veel verschillende talen vertaald door vrijwilligers. Je kunt helpen op {link}" +manageAccessTokens: "Toegangstokens beheren" +accountInfo: "Informatie over gebruikersaccount" +notesCount: "Aantal notities" +repliesCount: "Aantal verzonden replies" +renotesCount: "Aantal verzonden renotes" +repliedCount: "Aantal ontvangen replies" +renotedCount: "Aantal ontvangen renotes" +followingCount: "Aantal gevolgde accounts" +followersCount: "Aantal volgers" +sentReactionsCount: "Aantal verzonden reacties" +receivedReactionsCount: "Aantal ontvangen reacties" +pollVotesCount: "Aantal verzonden peiling stemmen" +pollVotedCount: "Aantal ontvangen peiling stemmen" +yes: "Ja" +no: "Nee" +driveFilesCount: "Aantal bestanden in station" +driveUsage: "Schijfruimtegebruik" +noCrawle: "Crawler-indexering verwerpen" +noCrawleDescription: "Vraag zoekmachines om je eigen profielpagina, notities, pagina's, enz. niet te indexeren." +lockedAccountInfo: "Tenzij je de zichtbaarheid van je notities instelt op “Alleen volgers”, zijn je notities zichtbaar voor iedereen, zelfs als je vereist dat volgers handmatig worden goedgekeurd." +alwaysMarkSensitive: "Markeer media standaard als gevoelig" +loadRawImages: "Toon altijd originele afbeeldingen in plaats van miniaturen" +disableShowingAnimatedImages: "Speel geen geanimeerde afbeeldingen af" +highlightSensitiveMedia: "Markeer gevoelige media" +verificationEmailSent: "Er is een bevestigingsmail naar uw e-mailadres verzonden. Ga naar de link in de e-mail om het verificatieproces te voltooien." +notSet: "Niet geconfigureerd" +emailVerified: "Emailadres bevestigd" +noteFavoritesCount: "Aantal notities gemarkeerd als favoriet" +pageLikesCount: "Aantal gelikete pagina's" +pageLikedCount: "Aantal ontvangen pagina-likes" +contact: "Contact" +useSystemFont: "Het standaardlettertype van het systeem gebruiken" +clips: "Clips" +experimentalFeatures: "Experimentele functionaliteiten" +experimental: "Experimentele" +thisIsExperimentalFeature: "Dit is een experimentele functie. De functionaliteit kan worden gewijzigd en werkt mogelijk niet zoals bedoeld." +developer: "Ontwikkelaar" +makeExplorable: "Gebruikersaccount zichtbaar maken in “Verkennen”" +makeExplorableDescription: "Als deze optie is uitgeschakeld, is uw gebruikersaccount niet zichtbaar in het gedeelte “Verkennen”." +duplicate: "Dupliceren" +left: "Links" +center: "Center" +wide: "Breed" +narrow: "Smal" reloadToApplySetting: "Deze instelling gaat pas in nadat de pagina herladen is. Nu herladen?" +needReloadToApply: "Deze instelling wordt van kracht nadat de pagina is vernieuwd." +showTitlebar: "Titelbalk weergeven" clearCache: "Cache opschonen" +onlineUsersCount: "{n} Gebruikers zijn online" +nUsers: "{n} Gebruikers" +nNotes: "{n} Notities" +sendErrorReports: "Foutrapporten sturen" +sendErrorReportsDescription: "Als u deze optie inschakelt, wordt gedetailleerde foutinformatie met Misskey gedeeld wanneer zich een probleem voordoet. Dit helpt de kwaliteit van Misskey te verbeteren.\nDit omvat informatie zoals de versie van uw OS, welke browser u gebruikt, uw activiteit in Misskey, enz." +myTheme: "Mijn thema" +backgroundColor: "Achtergrondkleur" +accentColor: "Accentkleur" +textColor: "Tekstkleur" +saveAs: "Opslaan als…" +advanced: "Geavanceerd" +advancedSettings: "Geavanceerde instellingen" +value: "Waarde" +createdAt: "Aangemaakt at" +updatedAt: "Laatst gewijzigd at" +saveConfirm: "Wijzigingen opslaan?" +deleteConfirm: "Echt verwijderen?" +invalidValue: "Ongeldige waarde." +registry: "Registry" +closeAccount: "Gebruikersaccount sluiten" +currentVersion: "Huidige versie" +latestVersion: "Nieuwste versie" +youAreRunningUpToDateClient: "Je gebruikt de nieuwste versie van je client." +newVersionOfClientAvailable: "Er is een nieuwere versie van je client beschikbaar." +usageAmount: "Gebruik" +capacity: "Capaciteit" +inUse: "Gebruikt" +editCode: "Code bewerken" +apply: "Toepassen" +receiveAnnouncementFromInstance: "Meldingen ontvangen van deze instantie" +emailNotification: "E-mailmeldingen" +publish: "Publiceren" +inChannelSearch: "In kanaal zoeken" +useReactionPickerForContextMenu: "Open reactieselectie door rechts te klikken" +typingUsers: "{users} is/zijn aan het schrijven..." +jumpToSpecifiedDate: "Naar een specifieke datum springen" +showingPastTimeline: "Momenteel wordt een oude tijdlijn weergeven" +clear: "Terugkeren" +markAllAsRead: "Alles als gelezen markeren" +goBack: "Terug" +unlikeConfirm: "Wil je echt je like verwijderen?" +fullView: "Volledig zicht" +quitFullView: "Volledig zicht verlaten" +addDescription: "Beschrijving toevoegen" +userPagePinTip: "Je kunt hier notities tonen door “Vastmaken aan profiel” te selecteren in het menu van de individuele notities." +notSpecifiedMentionWarning: "Deze notitie bevat verwijzingen naar gebruikers die niet zijn geselecteerd als ontvangers" info: "Over" +userInfo: "Gebruikersinformatie" +unknown: "Onbekend" +onlineStatus: "Online status" +hideOnlineStatus: "Online status verbergen" +hideOnlineStatusDescription: "Het verbergen van je online status vermindert het nut van functies zoals zoeken." +online: "Online" +active: "Actief" +offline: "Offline" +notRecommended: "Niet aanbevolen" +botProtection: "Beveiliging tegen bots" +instanceBlocking: "Geblokkeerde/gedempte Instanties" +selectAccount: "Gebruikersaccount selecteren" +switchAccount: "Account wisselen" +enabled: "Ingeschakeld" +disabled: "Uitgeschakeld" +quickAction: "Snelle acties" user: "Gebruikers" +administration: "Beheer" +accounts: "Gebruikersaccounts" +switch: "Wissel" +noMaintainerInformationWarning: "Operatorinformatie is niet geconfigureerd." noInquiryUrlWarning: "Contact-URL niet opgegeven" +noBotProtectionWarning: "Bescherming tegen bots is niet geconfigureerd." +configure: "Configureer" +postToGallery: "Nieuw galerijbericht maken" +postToHashtag: "Post naar deze hashtag" +gallery: "Galerij" +recentPosts: "Recente berichten" +popularPosts: "Populair berichten" +shareWithNote: "Delen met notitie" +ads: "Advertenties" +expiration: "Deadline" +startingperiod: "Start" +memo: "Memo" +priority: "Prioriteit" +high: "Hoge" +middle: "Medium" +low: "Lage" +emailNotConfiguredWarning: "E-mailadres niet ingesteld." +ratio: "Verhouding" +previewNoteText: "Show voorproefje" +customCss: "Aangepaste CSS" +customCssWarn: "Gebruik deze instelling alleen als je weet wat het doet. Ongeldige invoer kan ertoe leiden dat de client niet meer normaal functioneert." +global: "Globaal" +squareAvatars: "Toon profielfoto's as vierkant" +sent: "Verzonden" +received: "Ontvangen" +searchResult: "Zoekresultaten" +hashtags: "Hashtags" +troubleshooting: "Probleemoplossing" +useBlurEffect: "Vervagingseffecten in de UI gebruike" +learnMore: "Meer leren" +misskeyUpdated: "Misskey is bijgewerkt!" +whatIsNew: "Wijzigingen tonen" +translate: "Vertalen" +translatedFrom: "Vertaald uit {x}" +accountDeletionInProgress: "De verwijdering van je gebruikersaccount wordt momenteel verwerkt." +usernameInfo: "Een naam die kan worden gebruikt om je gebruikersaccount op deze server te identificeren. Je kunt het alfabet (a~z, A~Z), cijfers (0~9) of underscores (_) gebruiken. Gebruikersnamen kunnen later niet worden gewijzigd." +aiChanMode: "Ai Mode" +devMode: "Ontwikkelaar modus" +keepCw: "Inhoudswaarschuwingen behouden" +pubSub: "Pub/Sub Gebruikersaccounts" +lastCommunication: "Laatste communicatie" +resolved: "Opgelost" +unresolved: "Onopgelost" +breakFollow: "Volger verwijderen" +breakFollowConfirm: "Deze volger echt weghalen?" +itsOn: "Ingeschakeld" +itsOff: "Uitgeschakeld" +on: "Op" +off: "Uit" +emailRequiredForSignup: "Vereist e-mailadres voor aanmelding" +unread: "Ongelezen" +filter: "Filter" +controlPanel: "Controlepaneel" +manageAccounts: "Gebruikersaccounts beheren" +makeReactionsPublic: "Reactiegeschiedenis publiceren" +makeReactionsPublicDescription: "Hierdoor wordt de lijst met al je eerdere reacties openbaar." +classic: "Classic" muteThread: "Discussies dempen " unmuteThread: "Dempen van discussie ongedaan maken" followingVisibility: "Zichtbaarheid van gevolgden" followersVisibility: "Zichtbaarheid van volgers" +continueThread: "Bekijk draad voortzetting" +deleteAccountConfirm: "Je gebruikersaccount wordt onherroepelijk verwijderd. Wil je nog steeds doorgaan?" +incorrectPassword: "Onjuist wachtwoord." incorrectTotp: "Het eenmalige wachtwoord is incorrect of verlopen" +voteConfirm: "Bevestig je je stem op “{choice}”?" hide: "Verbergen" +useDrawerReactionPickerForMobile: "Toon reactiekiezer als lade op mobiel" +welcomeBackWithName: "Welkom terug, {name}" +clickToFinishEmailVerification: "Druk op [{ok}] om de e-mailbevestiging af te ronden." searchByGoogle: "Zoeken" threeMonths: "3 maanden" oneYear: "1 jaar" @@ -509,6 +938,7 @@ threeDays: "3 dagen" cropImage: "Afbeelding bijsnijden" cropImageAsk: "Bijsnijdengevraagd" file: "Bestanden" +account: "Gebruikersaccounts" pushNotification: "Pushberichten" subscribePushNotification: "Push meldingen inschakelen" unsubscribePushNotification: "Pushberichten uitschakelen" @@ -516,6 +946,7 @@ pushNotificationAlreadySubscribed: "Pushberichtrn al ingeschakeld" windowMaximize: "Maximaliseren" windowRestore: "Herstellen" loggedInAsBot: "Momenteel als bot ingelogd" +show: "Weergave" correspondingSourceIsAvailable: "De bijbehorende broncode is beschikbaar bij {anchor}" invalidParamErrorDescription: "De aanvraagparameters zijn ongeldig. Dit komt meestal door een bug, maar kan ook omdat de invoer te lang is of iets dergelijks." collapseRenotes: "Renotes die je al gezien hebt, inklappen" @@ -534,26 +965,43 @@ lookupConfirm: "Weet je zeker dat je dit wil opzoeken?" openTagPageConfirm: "Wil je deze hashtagpagina openen?" specifyHost: "Specificeer host" icon: "Avatar" -replies: "Antwoord" +replies: "Antwoorden" renotes: "Herdelen" followingOrFollower: "Gevolgd of volger" confirmShowRepliesAll: "Dit is een onomkeerbare operatie. Weet je zeker dat reacties op anderen van iedereen die je volgt, wil weergeven in je tijdlijn?" information: "Over" +_imageEditing: + _vars: + filename: "Bestandsnaam" _chat: invitations: "Uitnodigen" + noHistory: "Geen geschiedenis gevonden" members: "Leden" home: "Startpagina" + send: "Stuur" _delivery: stop: "Opgeschort" _type: none: "Publiceren" +_role: + priority: "Prioriteit" + _priority: + low: "Lage" + middle: "Medium" + high: "Hoge" +_ffVisibility: + public: "Publiceren" +_ad: + back: "Terug" _email: _follow: title: "volgde jou" _theme: + description: "Beschrijving" keys: mention: "Vermelding" renote: "Herdelen" + divider: "Scheider" _sfx: note: "Notities" notification: "Meldingen" @@ -569,15 +1017,23 @@ _widgets: jobQueue: "Job Queue" _userList: chooseList: "Kies een lijst." +_widgetOptions: + height: "Hoogte" _cw: show: "Laad meer" _visibility: home: "Startpagina" followers: "Volgers" + specified: "Directe notities" +_postForm: + _howToUse: + visibility_title: "Zichtbaarheid" + menu_title: "Menu" _profile: name: "Naam" username: "Gebruikersnaam" _exportOrImport: + clips: "Clip aanmaken" followingList: "Volgend" muteList: "Dempen" blockingList: "Blokkeren" @@ -588,6 +1044,9 @@ _charts: federation: "Federatie" _timelines: home: "Startpagina" +_play: + script: "Script" + summary: "Beschrijving" _pages: blocks: image: "Afbeeldingen" @@ -610,9 +1069,16 @@ _deck: tl: "Tijdlijn" antenna: "Antennes" list: "Lijsten" + channel: "Kanalen" mentions: "Vermeldingen" + direct: "Directe notities" _webhookSettings: name: "Naam" + active: "Ingeschakeld" +_abuseReport: + _notificationRecipient: + _recipientType: + mail: "Email" _moderationLogTypes: suspend: "Opschorten" resetPassword: "Wachtwoord terugzetten" @@ -623,3 +1089,8 @@ _remoteLookupErrors: title: "Niet gevonden" _search: searchScopeAll: "Alle" +_watermarkEditor: + image: "Afbeeldingen" + advanced: "Geavanceerd" +_qr: + showTabTitle: "Weergave" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 44b2cd2704..7bf3f387b8 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -171,7 +171,6 @@ noUsers: "Det er ingen brukere" editProfile: "Rediger profil" noteDeleteConfirm: "Er du sikker på at du vil slette denne Noten?" pinLimitExceeded: "Du kan ikke feste flere." -intro: "Installasjonen av Misskey er ferdig! Vennligst opprett en administratorkonto." done: "Ferdig" default: "Standard" defaultValueIs: "Standard: {value}" @@ -462,6 +461,14 @@ replies: "Svar" renotes: "Renote" surrender: "Avbryt" information: "Informasjon" +inMinutes: "Minutter" +inDays: "Dager" +_imageEditing: + _vars: + filename: "Filnavn" +_imageFrameEditor: + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "Inviter" members: "Medlemmer" @@ -632,6 +639,10 @@ _widgets: userList: "Brukerliste" _userList: chooseList: "Velg liste" +_widgetOptions: + height: "Høyde" + _clock: + size: "Størrelse" _cw: hide: "Skjul" show: "Vis mer" @@ -649,6 +660,8 @@ _visibility: home: "Hjem" followers: "Følgere" _postForm: + _howToUse: + menu_title: "Meny" _placeholders: a: "Hva skjer?" _profile: @@ -736,3 +749,15 @@ _remoteLookupErrors: title: "Ikke funnet" _search: searchScopeAll: "Alle" +_watermarkEditor: + scale: "Størrelse" + text: "Tekst" + type: "Type" + image: "Bilder" +_imageEffector: + _fxProps: + scale: "Størrelse" + size: "Størrelse" + color: "Farge" +_qr: + raw: "Tekst" diff --git a/locales/package.json b/locales/package.json deleted file mode 100644 index bedb411a91..0000000000 --- a/locales/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 46f8939137..34540239fb 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -230,7 +230,6 @@ noUsers: "Brak użytkowników" editProfile: "Edytuj profil" noteDeleteConfirm: "Czy na pewno chcesz usunąć ten wpis?" pinLimitExceeded: "Nie możesz przypiąć więcej wpisów." -intro: "Zakończono instalację Misskey! Utwórz konto administratora." done: "Gotowe" processing: "Przetwarzanie" preview: "Podgląd" @@ -749,7 +748,6 @@ thisIsExperimentalFeature: "Ta funkcja jest eksperymentalna. Jej funkcjonalnoś developer: "Programista" makeExplorable: "Pokazuj konto na stronie „Eksploruj”" makeExplorableDescription: "Jeżeli wyłączysz tę opcję, Twoje konto nie będzie wyświetlać się w sekcji „Eksploruj”." -showGapBetweenNotesInTimeline: "Pokazuj odstęp między wpisami na osi czasu." duplicate: "Duplikuj" left: "Lewo" center: "Wyśsrodkuj" @@ -1042,6 +1040,18 @@ surrender: "Odrzuć" gameRetry: "Spróbuj ponownie" postForm: "Formularz tworzenia wpisu" information: "Informacje" +inMinutes: "minuta" +inDays: "dzień" +widgets: "Widżety" +presets: "Konfiguracja" +_imageEditing: + _vars: + filename: "Nazwa pliku" +_imageFrameEditor: + header: "Nagłówek" + font: "Czcionka" + fontSerif: "Szeryfowa" + fontSansSerif: "Bezszeryfowa" _chat: invitations: "Zaproś" noHistory: "Brak historii" @@ -1233,7 +1243,6 @@ _theme: buttonBg: "Tło przycisku" buttonHoverBg: "Tło przycisku (po najechaniu)" inputBorder: "Obramowanie pola wejścia" - driveFolderBg: "Tło folderu na dysku" badge: "Odznaka" messageBg: "Tło czatu" fgHighlighted: "Wyróżniony tekst" @@ -1352,6 +1361,14 @@ _widgets: _userList: chooseList: "Wybierz listę" clicker: "Clicker" +_widgetOptions: + height: "Wysokość" + _button: + colored: "Kolorowe" + _clock: + size: "Rozmiar" + _birthdayFollowings: + period: "Czas trwania" _cw: hide: "Ukryj" show: "Załaduj więcej" @@ -1393,6 +1410,9 @@ _postForm: replyPlaceholder: "Odpowiedz na ten wpis..." quotePlaceholder: "Zacytuj ten wpis…" channelPlaceholder: "Publikuj na kanale..." + _howToUse: + visibility_title: "Widoczność" + menu_title: "Menu" _placeholders: a: "Co się dzieje?" b: "Co się wydarzyło?" @@ -1587,3 +1607,20 @@ _remoteLookupErrors: _search: searchScopeAll: "Wszystkie" searchScopeLocal: "Lokalne" +_watermarkEditor: + opacity: "Przezroczystość" + scale: "Rozmiar" + text: "Tekst" + type: "Typ" + image: "Zdjęcia" + advanced: "Zaawansowane" +_imageEffector: + _fxProps: + scale: "Rozmiar" + size: "Rozmiar" + color: "Kolor" + opacity: "Przezroczystość" + lightness: "Rozjaśnij" +_qr: + showTabTitle: "Wyświetlanie" + raw: "Tekst" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index e899685d9f..7552852c4e 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -5,6 +5,7 @@ introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto Misskey." monthAndDay: "{day}/{month}" search: "Pesquisar" +reset: "Redefinir" notifications: "Notificações" username: "Nome de usuário" password: "Senha" @@ -48,6 +49,7 @@ pin: "Fixar no perfil" unpin: "Desafixar do perfil" copyContent: "Copiar conteúdos" copyLink: "Copiar link" +copyRemoteLink: "Copiar endereço remoto" copyLinkRenote: "Copiar o link da repostagem" delete: "Excluir" deleteAndEdit: "Excluir e editar" @@ -141,9 +143,9 @@ deleteFile: "Excluir arquivo" markAsSensitive: "Marcar como sensível" unmarkAsSensitive: "Desmarcar como sensível" enterFileName: "Digite o nome do arquivo" -mute: "Mutar" +mute: "Silenciar" unmute: "Desmutar" -renoteMute: "Mutar repostagens" +renoteMute: "Silenciar repostagens" renoteUnmute: "Reativar repostagens" block: "Bloquear" unblock: "Desbloquear" @@ -218,6 +220,7 @@ silenceThisInstance: "Silenciar essa instância" mediaSilenceThisInstance: "Silenciar a mídia dessa instância" operations: "Operações" software: "Software" +softwareName: "Software" version: "Versão" metadata: "Metadados" withNFiles: "{n} arquivo(s)" @@ -248,9 +251,9 @@ noUsers: "Sem usuários" editProfile: "Editar Perfil" noteDeleteConfirm: "Deseja excluir esta nota?" pinLimitExceeded: "Não é possível fixar novas notas" -intro: "A instalação do Misskey está completa! Crie uma conta de administrador." done: "Concluído" processing: "Em Progresso" +preprocessing: "Preparando..." preview: "Pré-visualizar" default: "Predefinição" defaultValueIs: "Predefinição: {value}" @@ -296,9 +299,11 @@ uploadFromUrl: "Enviar por URL" uploadFromUrlDescription: "URL do arquivo que você deseja enviar" uploadFromUrlRequested: "Upload solicitado" uploadFromUrlMayTakeTime: "Pode levar algum tempo para que o upload seja concluído." +uploadNFiles: "Enviar {n} arquivos" explore: "Explorar" messageRead: "Lida" noMoreHistory: "Não existe histórico anterior" +startChat: "Iniciar conversa" nUsersRead: "{n} pessoas leram" agreeTo: "Eu concordo com {0}" agree: "Concordar" @@ -323,6 +328,7 @@ dark: "Escuro" lightThemes: "Tema claro" darkThemes: "Tema escuro" syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" está ativado. Você gostaria de desligar a sincronização e alterar manualmente?" drive: "Drive" fileName: "Nome do Ficheiro" selectFile: "Selecione os arquivos" @@ -421,6 +427,7 @@ antennaExcludeBots: "Ignorar contas de bot" antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR." notifyAntenna: "Notificar novas notas" withFileAntenna: "Apenas notas com arquivos anexados" +excludeNotesInSensitiveChannel: "Excluir notas de canais sensíveis" enableServiceworker: "Ative as notificações push para o seu navegador" antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha" caseSensitive: "Maiúsculas e minúsculas" @@ -440,7 +447,7 @@ exploreUsersCount: "Há um utilizador de {count}" exploreFediverse: "Explorar Fediverse" popularTags: "Tags populares" userList: "Listas" -about: "Informações" +about: "Sobre" aboutMisskey: "Sobre Misskey" administrator: "Administrador" token: "Símbolo" @@ -571,8 +578,10 @@ showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha showFixedPostFormInChannel: "Exibir o campo de postagem na parte superior da linha do tempo (canais)" withRepliesByDefaultForNewlyFollowed: "Incluir respostas por usuários recém-seguidos na linha do tempo por padrão" newNoteRecived: "Nova nota recebida" +newNote: "Nova Nota" sounds: "Sons" sound: "Sons" +notificationSoundSettings: "Configurações de som de notificações" listen: "Ouvir" none: "Nenhum" showInPage: "Ver na página" @@ -680,14 +689,19 @@ smtpSecure: "Use SSL/TLS implícito para conexões SMTP" smtpSecureInfo: "Desative esta opção ao utilizar STARTTLS." testEmail: "Testar envio de e-mail" wordMute: "Silenciar palavras" +wordMuteDescription: "Minimizar notas que contêm a palavra ou frase especificada. Notas minimizadas são exibidas ao clicá-las." hardWordMute: "Silenciar palavras (esconder posts)" +showMutedWord: "Exibir palavras silenciadas" +hardWordMuteDescription: "Esconder notas que contêm a palavra ou frase especificada. Diferente do silenciamento de palavras, a nota será completamente escondida." regexpError: "Erro na expressão regular" regexpErrorDescription: "Ocorreu um erro na expressão regular na linha {line} da palavra mutada {tab}:" instanceMute: "Instâncias silenciadas" userSaysSomething: "{name} disse algo" +userSaysSomethingAbout: "{name} disse algo sobre \"{word}\"" makeActive: "Ativar" display: "Visualizar" copy: "Copiar" +copiedToClipboard: "Copiado à área de transferência" metrics: "Métricas" overview: "Visão geral" logs: "Logs" @@ -775,7 +789,6 @@ thisIsExperimentalFeature: "Este é um recurso experimental. As funções podem developer: "Programador" makeExplorable: "Deixe a sua conta encontrável em \"Explorar\"." makeExplorableDescription: "Se você desativá-lo, outros usuários não poderão encontrar a sua conta na aba Descoberta." -showGapBetweenNotesInTimeline: "Mostrar um espaço entre as notas na linha de tempo" duplicate: "Duplicar" left: "Esquerda" center: "Centralizar" @@ -783,6 +796,7 @@ wide: "Largo" narrow: "Estreito" reloadToApplySetting: "As configurações serão refletidas após recarregar a página. Deseja recarregar agora?" needReloadToApply: "É necessário recarregar a página para refletir as alterações." +needToRestartServerToApply: "É necessário reiniciar o servidor para aplicar as mudanças." showTitlebar: "Exibir barra de título" clearCache: "Limpar o cache" onlineUsersCount: "{n} Pessoas Online" @@ -970,6 +984,7 @@ document: "Documentação" numberOfPageCache: "Número de cache de página" numberOfPageCacheDescription: "Aumentar isso melhora a conveniência, mas também resulta em maior carga e uso de memória." logoutConfirm: "Gostaria de encerrar a sessão?" +logoutWillClearClientData: "Sair irá remover as configurações do cliente do navegador. Para redefinir as configurações ao entrar, você deve habilitar o backup automático de configurações." lastActiveDate: "Última data de uso" statusbar: "Barra de status" pleaseSelect: "Por favor, selecione." @@ -988,6 +1003,7 @@ failedToUpload: "Falha ao enviar" cannotUploadBecauseInappropriate: "Esse arquivo não pôde ser enviado porque partes dele foram detectadas como potencialmente inapropriadas." cannotUploadBecauseNoFreeSpace: "Envio falhou devido à falta de capacidade no Drive." cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido." +cannotUploadBecauseUnallowedFileType: "Não foi possível fazer o envio, pois o formato do arquivo não foi autorizado." beta: "Beta" enableAutoSensitive: "Marcar automaticamente como conteúdo sensível" enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente." @@ -1039,6 +1055,7 @@ permissionDeniedError: "Operação recusada" permissionDeniedErrorDescription: "Esta conta não tem permissão para executar esta ação." preset: "Predefinições" selectFromPresets: "Escolher de predefinições" +custom: "Personalizado" achievements: "Conquistas" gotInvalidResponseError: "Resposta do servidor inválida" gotInvalidResponseErrorDescription: "Servidor fora do ar ou em manutenção. Favor tentar mais tarde." @@ -1077,6 +1094,7 @@ prohibitedWordsDescription2: "Utilizar espaços irá criar expressões aditivas hiddenTags: "Hashtags escondidas" hiddenTagsDescription: "Selecione tags que não serão exibidas na lista de destaques. Várias tags podem ser escolhidas, separadas por linha." notesSearchNotAvailable: "A pesquisa de notas está indisponível." +usersSearchNotAvailable: "Pesquisa de usuário está indisponível." license: "Licença" unfavoriteConfirm: "Deseja realmente remover dos favoritos?" myClips: "Meus clipes" @@ -1227,9 +1245,8 @@ showAvatarDecorations: "Exibir decorações de avatar" releaseToRefresh: "Solte para atualizar" refreshing: "Atualizando..." pullDownToRefresh: "Puxe para baixo para atualizar" -disableStreamingTimeline: "Desabilitar atualizações em tempo real da linha do tempo" useGroupedNotifications: "Agrupar notificações" -signupPendingError: "Houve um problema ao verificar o endereço de email. O link pode ter expirado." +emailVerificationFailedError: "Houve um problema ao verificar seu endereço de email. O link pode ter expirado." cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada." doReaction: "Adicionar reação" code: "Código" @@ -1297,16 +1314,212 @@ lockdown: "Lockdown" pleaseSelectAccount: "Selecione uma conta" availableRoles: "Cargos disponíveis" acknowledgeNotesAndEnable: "Ative após compreender as precauções." +federationSpecified: "Esse servidor opera com uma lista branca de federação. Interagir com servidores diferentes daqueles designados pela administração não é permitido." +federationDisabled: "Federação está desabilitada nesse servidor. Você não pode interagir com usuários de outros servidores." +draft: "Rascunhos" +draftsAndScheduledNotes: "Rascunhos e notas agendadas." +confirmOnReact: "Confirmar ao reagir" +reactAreYouSure: "Você deseja adicionar uma reação \"{emoji}\"?" +markAsSensitiveConfirm: "Você deseja definir essa mídia como sensível?" +unmarkAsSensitiveConfirm: "Você deseja remover a definição dessa mídia como sensível?" +preferences: "Preferências" +accessibility: "Acessibilidade" +preferencesProfile: "Perfil de preferências" +copyPreferenceId: "Copiar ID de preferências" +resetToDefaultValue: "Reverter ao padrão" +overrideByAccount: "Sobrescrever pela conta" +untitled: "Sem título" +noName: "Sem nome" +skip: "Pular" +restore: "Redefinir" +syncBetweenDevices: "Sincronizar entre dispositivos" +preferenceSyncConflictTitle: "O valor configurado já existe no servidor." +preferenceSyncConflictText: "As preferências com a sincronização ativada irão salvar os seus valores no servidor. Porém, já existem valores no servidor. Qual conjunto de valores você deseja sobrescrever?" +preferenceSyncConflictChoiceMerge: "Combinar" +preferenceSyncConflictChoiceServer: "Valor configurado no servidor" +preferenceSyncConflictChoiceDevice: "Valor configurado no dispositivo" +preferenceSyncConflictChoiceCancel: "Cancelar a habilitação de sincronização" +paste: "Colar" +emojiPalette: "Paleta de emojis" postForm: "Campo de postagem" -information: "Informações" +textCount: "Contagem de caracteres" +information: "Sobre" +chat: "Conversas" +directMessage: "Conversar com usuário" +directMessage_short: "Mensagem" +migrateOldSettings: "Migrar configurações antigas de cliente" +migrateOldSettings_description: "Isso deve ser feito automaticamente. Caso o processo de migração tenha falhado, você pode acioná-lo manualmente. As informações atuais de migração serão substituídas." +compress: "Comprimir" +right: "Direita" +bottom: "Inferior" +top: "Superior" +embed: "Embed" +settingsMigrating: "Configurações estão sendo migradas, aguarde... (Você pode migrar manualmente em Configurações→Outros→Migrar configurações antigas de cliente)" +readonly: "Ler apenas" +goToDeck: "Voltar ao Deck" +federationJobs: "Tarefas de Federação" +driveAboutTip: "No Drive, uma lista de arquivos enviados no passado será exibida.
\nVocê pode reutilizar esses arquivos anexando-os às notas, ou você pode enviar arquivos para publicar posteriormente.
\nCuidado ao excluir um arquivo, pois ele será removido de quaisquer outros lugares onde está sendo utilizado (notas, páginas, avatares, banners, etc.)
\nVocê também pode criar pastas para organizar seus arquivos." +scrollToClose: "Role a página para fechar" +advice: "Dica" +realtimeMode: "Modo tempo-real" +turnItOn: "Ativar" +turnItOff: "Desativar" +emojiMute: "Silenciar emoji" +emojiUnmute: "Reativar emoji" +muteX: "Silenciar {x}" +unmuteX: "Reativar {x}" +abort: "Abortar" +tip: "Dicas e Truques" +redisplayAllTips: "Mostrar todas as \"Dicas e Truques\" novamente" +hideAllTips: "Ocultas todas as \"Dicas e Truques\"" +defaultImageCompressionLevel: "Nível de compressão de imagem padrão" +defaultImageCompressionLevel_description: "Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem.
Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem." +defaultCompressionLevel: "Nível padrão de compressão" +defaultCompressionLevel_description: "Menor compressão preserva a qualidade mas aumenta o tamanho do arquivo.
Maior compressão reduz o tamanho do arquivo mas diminui a qualidade." +inMinutes: "Minuto(s)" +inDays: "Dia(s)" +safeModeEnabled: "Modo seguro está habilitado" +pluginsAreDisabledBecauseSafeMode: "Todos os plugins estão desabilitados porque o modo seguro está habilitado." +customCssIsDisabledBecauseSafeMode: "CSS personalizado não está aplicado porque o modo seguro está habilitado." +themeIsDefaultBecauseSafeMode: "Enquanto o modo seguro estiver ativo, o tema padrão é utilizado. Desabilitar o modo seguro reverterá essas mudanças." +thankYouForTestingBeta: "Obrigado por nos ajudar a testar a versão beta!" +createUserSpecifiedNote: "Criar uma nota direta" +schedulePost: "Agendar publicação" +scheduleToPostOnX: "Agendar nota para {x}" +scheduledToPostOnX: "A nota está agendada para {x}" +schedule: "Agendar" +scheduled: "Agendado" +widgets: "Widgets" +presets: "Predefinições" +_imageEditing: + _vars: + filename: "Nome do Ficheiro" +_imageFrameEditor: + header: "Cabeçalho" + withQrCode: "Código QR" + font: "Fonte" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "Descartar mudanças?" +_compression: + _quality: + high: "Qualidade alta" + medium: "Qualidade média" + low: "Qualidade baixa" + _size: + large: "Tamanho grande" + medium: "Tamanho médio" + small: "Tamanho pequeno" +_order: + newest: "Priorizar Mais Novos" + oldest: "Priorizar Mais Antigos" _chat: + messages: "Mensagem" + noMessagesYet: "Ainda não há mensagens" + newMessage: "Nova mensagem" + individualChat: "Conversa Particular" + individualChat_description: "Ter uma conversa particular com outra pessoa." + roomChat: "Conversa de Grupo" + roomChat_description: "Uma sala de conversas com várias pessoas. Você pode adicionar pessoas que não permitem conversas privadas se elas aceitarem o convite." + createRoom: "Criar Sala" + inviteUserToChat: "Convide usuários para começar a conversar" + yourRooms: "Salas criadas" + joiningRooms: "Salas ingressadas" invitations: "Convidar" + noInvitations: "Sem convites" + history: "Histórico" noHistory: "Ainda não há histórico" + noRooms: "Nenhuma sala encontrada" + inviteUser: "Convidar Usuários" + sentInvitations: "Convites Enviados" + join: "Entrar" + ignore: "Ignorar" + leave: "Deixar sala" members: "Membros" + searchMessages: "Pesquisar mensagens" home: "Início" send: "Enviar" + newline: "Nova linha" + muteThisRoom: "Silenciar sala" + deleteRoom: "Excluir sala" + chatNotAvailableForThisAccountOrServer: "Conversas não estão habilitadas nesse servidor ou para essa conta." + chatIsReadOnlyForThisAccountOrServer: "Conversas são apenas para leitura nesse servidor ou para essa conta. Não é possível escrever novas mensagens ou criar/ingressar novas conversas." + chatNotAvailableInOtherAccount: "A função de conversas está desabilitadas para o outro usuário." + cannotChatWithTheUser: "Não é possível conversar com esse usuário." + cannotChatWithTheUser_description: "Conversas estão indisponíveis ou o outro usuário não as habilitou." + youAreNotAMemberOfThisRoomButInvited: "Você não é um participante da sala, mas recebeu um convite. Por favor, aceite o convite para entrar." + doYouAcceptInvitation: "Aceita o convite?" + chatWithThisUser: "Conversar com usuário" + thisUserAllowsChatOnlyFromFollowers: "Esse usuário aceita conversar apenas com seguidores." + thisUserAllowsChatOnlyFromFollowing: "Esse usuário aceita conversar apenas com quem segue." + thisUserAllowsChatOnlyFromMutualFollowing: "Esse usuário aceita conversar apenas com seguidores mútuos." + thisUserNotAllowedChatAnyone: "Esse usuário não aceita conversar com ninguém." + chatAllowedUsers: "Com quem permitir conversas" + chatAllowedUsers_note: "Você pode conversar com qualquer um com quem tenha iniciado uma conversa independente dessa configuração." + _chatAllowedUsers: + everyone: "Todos" + followers: "Seus seguidores" + following: "Quem você segue" + mutual: "Seguidores mútuos" + none: "Ninguém" +_emojiPalette: + palettes: "Paleta" + enableSyncBetweenDevicesForPalettes: "Sincronizar paleta entre dispositivos" + paletteForMain: "Paleta principal" + paletteForReaction: "Paleta de reações" _settings: + driveBanner: "Você consegue administrar e configurar o drive, conferir o seu uso e configurar as opções de envio de arquivos." + pluginBanner: "Você pode ampliar as funções do cliente com plugins. Você pode instalar plugins, configurar e administrar individualmente." + notificationsBanner: "Você pode configurar os tipos e intervalo das notificações do servidor, além de notificações push." + api: "API" webhook: "Webhook" + serviceConnection: "Integração de serviço" + serviceConnectionBanner: "Administre e configure tokens de acesso e webhooks para interagir com aplicações e serviços externos." + accountData: "Dados da conta" + accountDataBanner: "Exportar e importar dados da conta." + muteAndBlockBanner: "Você pode configurar meios para esconder conteúdo e restringir ações de certos usuários." + accessibilityBanner: "Você pode personalizar o visual e comportamento do cliente, além de configurar modos de otimizar o uso." + privacyBanner: "Você pode configurar a privacidade da conta por meio da visibilidade do conteúdo, capacidade de descoberta e aprovação manual de seguidores." + securityBanner: "Você pode configurar a segurança da conta em ajustes como senha, meios de entrada, aplicativos de autenticação e chaves de acesso." + preferencesBanner: "Você pode configurar o comportamento geral do cliente segundo as suas preferências." + appearanceBanner: "Você pode configurar a aparência do cliente e ajustes de tela segundo as suas preferências." + soundsBanner: "Você pode configurar a reprodução de sons no cliente." + timelineAndNote: "Notas e linha do tempo" + makeEveryTextElementsSelectable: "Tornar todos os elementos de texto selecionáveis" + makeEveryTextElementsSelectable_description: "Habilitar isso pode reduzir a usabilidade em algumas situações" + useStickyIcons: "Fazer ícones acompanharem a rolagem da tela" + enableHighQualityImagePlaceholders: "Exibir prévias para imagens de alta qualidade" + uiAnimations: "Animações de UI" + showNavbarSubButtons: "Mostrar sub-botões na barra de navegação" + ifOn: "Quando ligado" + ifOff: "Quando desligado" + enableSyncThemesBetweenDevices: "Sincronizar temas instalados entre dispositivos" + enablePullToRefresh: "Puxe para atualizar" + enablePullToRefresh_description: "Quando estiver utilizando um mouse, arraste enquanto aperta a roda de rolagem." + realtimeMode_description: "Estabelece uma conexão com o servidor e atualiza o conteúdo em tempo real. Isso pode aumentar o tráfego e uso de memória." + contentsUpdateFrequency: "Frequência da obtenção de conteúdo" + contentsUpdateFrequency_description: "Quanto maior o valor, mais o conteúdo atualiza. Porém, há uma diminuição do desempenho e aumento do tráfego e consumo de memória." + contentsUpdateFrequency_description2: "Quando o modo tempo-real está ativado, o conteúdo é atualizado em tempo real, ignorando essa opção." + showUrlPreview: "Exibir prévia de URL" + showAvailableReactionsFirstInNote: "Exibir reações disponíveis no topo." + showPageTabBarBottom: "Mostrar barra de aba da página inferiormente" + _chat: + showSenderName: "Exibir nome de usuário do remetente" + sendOnEnter: "Pressionar Enter para enviar" +_preferencesProfile: + profileName: "Nome do perfil" + profileNameDescription: "Defina o nome que identifica esse dispositivo." + profileNameDescription2: "Exemplo: \"Computador Principal\", \"Celular\"" + manageProfiles: "Gerenciar Perfis" +_preferencesBackup: + autoBackup: "Backup automático" + restoreFromBackup: "Restaurar backup" + noBackupsFoundTitle: "Nenhum backup encontrado" + noBackupsFoundDescription: "Nenhum backup automático foi encontrado. Se você salvou um arquivo de backup manualmente, você pode importá-lo e restaurá-lo." + selectBackupToRestore: "Selecionar um backup para restaurar" + youNeedToNameYourProfileToEnableAutoBackup: "Um nome de perfil deve ser definido para habilitar o backup automático." + autoPreferencesBackupIsNotEnabledForThisDevice: "Backup automático de configurações não está habilitado no dispositivo." + backupFound: "Backup de configurações encontrado" _accountSettings: requireSigninToViewContents: "Exigir cadastro para ver o conteúdo" requireSigninToViewContentsDescription1: "Exigir cadastro para ver todas as notas e outro conteúdo que você criou. Isso previne 'crawlers' de coletar os seus dados." @@ -1317,6 +1530,7 @@ _accountSettings: makeNotesHiddenBefore: "Tornar notas passadas privadas" makeNotesHiddenBeforeDescription: "Com essa função ativada, apenas você poderá ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido." mayNotEffectForFederatedNotes: "Notas federadas a servidores remotos podem não ser afetadas." + mayNotEffectSomeSituations: "Essas restrições são simplificadas. Elas podem não ser aplicadas em algumas situações, como ao visualizar num servidor remoto ou durante a moderação." notesHavePassedSpecifiedPeriod: "Notas que duraram um tempo específico." notesOlderThanSpecifiedDateAndTime: "Notas antes do tempo específico." _abuseUserReport: @@ -1335,6 +1549,7 @@ _delivery: manuallySuspended: "Suspenso manualmente" goneSuspended: "Servidor foi suspenso devido ao seu apagamento" autoSuspendedForNotResponding: "Servidor foi suspenso por não responder" + softwareSuspended: "Suspenso, pois esse software não está recebendo conteúdo" _bubbleGame: howToPlay: "Como jogar" hold: "Próximos" @@ -1461,11 +1676,37 @@ _serverSettings: fanoutTimelineDbFallback: "\"Fallback\" ao banco de dados" fanoutTimelineDbFallbackDescription: "Quando habilitado, a linha do tempo irá recuar ao banco de dados caso consultas adicionais sejam feitas e ela não estiver em cache. Quando desabilitado, o impacto no servidor será reduzido ao eliminar o recuo, mas limita a quantidade de linhas do tempo que podem ser recebidas." reactionsBufferingDescription: "Quando ativado, o desempenho durante a criação de uma reação será melhorado substancialmente, reduzindo a carga do banco de dados. Porém, a o uso de memória do Redis irá aumentar." + remoteNotesCleaning: "Limpeza automática de notas remotas" + remoteNotesCleaning_description: "Quando habilitado, notas remotas obsoletas e não utilizadas serão periodicamente limpadas para previnir sobrecarga no banco de dados." + remoteNotesCleaningMaxProcessingDuration: "Maximizar tempo de processamento da limpeza" + remoteNotesCleaningExpiryDaysForEachNotes: "Mínimo de dias para retenção de notas" inquiryUrl: "URL de inquérito" inquiryUrlDescription: "Especifique um URL para um formulário de inquérito para a administração ou uma página web com informações de contato." openRegistration: "Abrir a criação de contas" openRegistrationWarning: "Abrir cadastros contém riscos. É recomendado apenas habilitá-los se houver um sistema de monitoramento contínuo e resolução imediata de problemas." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Se nenhuma atividade da moderação for detectada por um tempo, essa configuração será desativada para prevenir spam." + deliverSuspendedSoftware: "Software Suspenso" + deliverSuspendedSoftwareDescription: "Você pode especificar uma faixa de nomes e versões do software de servidores para cancelar o envio de conteúdo por motivos como vulnerabilidades. Essa informação da versão é providenciada pelo servidor e pode não ser confiável. Uma faixa semver pode ser utilizada para especificar a versão, mas colocar '>= 2024.3.1' não incluirá versões personalizadas como '2024.3.1-custom.0'. Logo, é recomendado inserir uma especificação como '>= 2024.3.1-0'" + singleUserMode: "Modo de usuário único" + singleUserMode_description: "Se você é o único usuário desse servidor, habilitar esse modo irá otimizar a performance." + signToActivityPubGet: "Assinar solicitações GET do ActivityPub" + signToActivityPubGet_description: "Normalmente, isso deve ser habilitado. Desabilitar pode melhorar o desempenho na federação, mas também pode cortar a federação com alguns servidores." + proxyRemoteFiles: "Passar arquivos remotos por proxy" + proxyRemoteFiles_description: "Se habilitado, o servidor irá servir arquivos remotos através de um proxy. Isso é útil para gerar prévias de imagens e proteger a privacidade do usuário." + allowExternalApRedirect: "Permitir redirecionamento de conteúdo pelo ActivityPub" + allowExternalApRedirect_description: "Se habilitado, outros servidores podem solicitar conteúdo de terceiros através desse servidor, o que pode resultar em falsificação de conteúdo (spoofing)." + userGeneratedContentsVisibilityForVisitor: "Visibilidade de conteúdo dos usuários para visitantes" + userGeneratedContentsVisibilityForVisitor_description: "Isso é útil para prevenir problemas causados por conteúdo inapropriado de usuários remotos de servidores com pouca ou nenhuma moderação, que pode ser hospedado na internet a partir desse servidor." + userGeneratedContentsVisibilityForVisitor_description2: "Publicar todo o conteúdo do servidor para a internet pode ser arriscado. Isso é especialmente importante para visitantes que desconhecem a natureza distribuída do conteúdo na internet, pois eles podem acreditar que o conteúdo remoto é criado por usuários desse servidor." + restartServerSetupWizardConfirm_title: "Reiniciar o assistente de configuração?" + restartServerSetupWizardConfirm_text: "Algumas configurações atuais serão reiniciadas." + entrancePageStyle: "Estilo da página de entrada" + showTimelineForVisitor: "Mostrar linha do tempo" + showActivitiesForVisitor: "Mostrar atividades" + _userGeneratedContentsVisibilityForVisitor: + all: "Tudo é público" + localOnly: "Conteúdo local é publicado, conteúdo remoto é privado" + none: "Tudo é privado" _accountMigration: moveFrom: "Migrar outra conta para essa" moveFromSub: "Criar um 'alias' a outra conta" @@ -1762,6 +2003,8 @@ _role: descriptionOfIsExplorable: "Ao ativar, a lista de membros será pública na seção 'Explorar' e a linha do tempo do cargo ficará disponível." displayOrder: "Ordenação" descriptionOfDisplayOrder: "Quanto maior o número, maior a posição de destaque na interface do usuário." + preserveAssignmentOnMoveAccount: "Preservar a associação de cargos durante a migração" + preserveAssignmentOnMoveAccount_description: "Quando ligado, esse cargo será encaminhado para a conta final quando houver migração de um usuário." canEditMembersByModerator: "Permitir a edição de membros deste cargo por moderadores" descriptionOfCanEditMembersByModerator: "Quando ativado, os moderadores também poderão atribuir/remover usuários deste papel, além dos administradores. Quando desativado, apenas os administradores poderão fazê-lo." priority: "Prioridade" @@ -1781,6 +2024,7 @@ _role: canManageCustomEmojis: "Permitir gerenciar emojis personalizados" canManageAvatarDecorations: "Gerenciar decorações de avatar" driveCapacity: "Capacidade do drive" + maxFileSize: "Tamanho máximo de envio de arquivos" alwaysMarkNsfw: "Sempre marcar arquivos como NSFW" canUpdateBioMedia: "Permitir a edição de ícone ou imagem do banner." pinMax: "Número máximo de notas fixadas" @@ -1795,6 +2039,7 @@ _role: descriptionOfRateLimitFactor: "Valores menores são menos restritivos, valores maiores são mais restritivos." canHideAds: "Permitir ocultar anúncios" canSearchNotes: "Permitir a busca de notas" + canSearchUsers: "Busca de usuário" canUseTranslator: "Uso do tradutor" avatarDecorationLimit: "Número máximo de decorações de avatar que podem ser aplicadas" canImportAntennas: "Permitir importação de antenas" @@ -1802,6 +2047,13 @@ _role: canImportFollowing: "Permitir importação de usuários seguidos" canImportMuting: "Permitir importação de silenciamentos" canImportUserLists: "Permitir importação de listas" + chatAvailability: "Permitir Conversas" + uploadableFileTypes: "Tipos de arquivo enviáveis" + uploadableFileTypes_caption: "Especifica tipos MIME permitidos. Múltiplos tipos MIME podem ser especificados separando-os por linha. Curingas podem ser especificados com um asterisco (*). (exemplo, image/*)" + uploadableFileTypes_caption2: "Alguns tipos de arquivos podem não ser detectados. Para permiti-los, adicione {x} à especificação." + noteDraftLimit: "Limite de rascunhos possíveis" + scheduledNoteLimit: "Número máximo de notas agendadas simultâneas" + watermarkAvailable: "Disponibilidade da função de marca d'água" _condition: roleAssignedTo: "Atribuído a cargos manuais" isLocal: "Usuário local" @@ -1961,10 +2213,12 @@ _theme: install: "Instalar um tema" manage: "Gerenciar temas" code: "Código do tema" + copyThemeCode: "Copiar código do tema" description: "Descrição" installed: "{name} foi instalado" installedThemes: "Temas instalados" builtinThemes: "Temas nativos" + instanceTheme: "Tema do servidor" alreadyInstalled: "Esse tema já foi instalado" invalid: "O formato desse tema é invalido" make: "Fazer um tema" @@ -2018,7 +2272,6 @@ _theme: buttonBg: "Plano de fundo de botão" buttonHoverBg: "Plano de fundo de botão (Selecionado)" inputBorder: "Borda de campo digitável" - driveFolderBg: "Plano de fundo da pasta no Drive" badge: "Emblema" messageBg: "Plano de fundo do chat" fgHighlighted: "Texto Destacado" @@ -2027,6 +2280,7 @@ _sfx: noteMy: "Própria nota" notification: "Notificações" reaction: "Ao selecionar uma reação" + chatMessage: "Mensagens em Conversas" _soundSettings: driveFile: "Usar um arquivo de áudio do Drive." driveFileWarn: "Selecione um arquivo de áudio do Drive." @@ -2059,6 +2313,7 @@ _time: minute: "Minuto(s)" hour: "Hora(s)" day: "Dia(s)" + month: "Mês(es)" _2fa: alreadyRegistered: "Você já cadastrou um dispositivo de autenticação de dois fatores." registerTOTP: "Cadastrar aplicativo autenticador" @@ -2174,6 +2429,7 @@ _permissions: "read:federation": "Ver dados de federação" "write:report-abuse": "Reportar violação" "write:chat": "Compor ou editar mensagens de chat" + "read:chat": "Navegar Conversas" _auth: shareAccessTitle: "Conceder permissões do aplicativo" shareAccess: "Você gostaria de autorizar \"{name}\" para acessar essa conta?" @@ -2232,6 +2488,16 @@ _widgets: chooseList: "Selecione uma lista" clicker: "Clicker" birthdayFollowings: "Usuários de aniversário hoje" + chat: "Conversar com usuário" +_widgetOptions: + showHeader: "Exibir cabeçalho" + height: "Altura" + _button: + colored: "Colorido" + _clock: + size: "Tamanho" + _birthdayFollowings: + period: "Duração" _cw: hide: "Esconder" show: "Carregar mais" @@ -2271,9 +2537,14 @@ _visibility: disableFederation: "Defederar" disableFederationDescription: "Não transmitir às outras instâncias" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Há arquivos que não foram enviados, gostaria de descartá-los e fechar o editor?" + uploaderTip: "O arquivo ainda não foi enviado. No menu do arquivo, você pode renomear, cortar, adicionar uma marca d'água, comprimir ou descomprimir um arquivo. Arquivos serão enviados automaticamente ao publicar a nota." replyPlaceholder: "Responder a essa nota..." quotePlaceholder: "Citar essa nota..." channelPlaceholder: "Postar em canal..." + _howToUse: + visibility_title: "Visibilidade" + menu_title: "Menu\n" _placeholders: a: "Como vão as coisas?" b: "O que está rolando por aí?" @@ -2419,9 +2690,12 @@ _notification: youReceivedFollowRequest: "Você recebeu um pedido de seguidor" yourFollowRequestAccepted: "Seu pedido de seguidor foi aceito" pollEnded: "Os resultados da enquete agora estão disponíveis" + scheduledNotePosted: "Nota agendada foi publicada" + scheduledNotePostFailed: "Não foi possível publicar nota agendada" newNote: "Nova nota" unreadAntennaNote: "Antena {name}" roleAssigned: "Cargo dado" + chatRoomInvitationReceived: "Você foi convidado para uma conversa" emptyPushNotificationMessage: "As notificações de alerta foram atualizadas" achievementEarned: "Conquista desbloqueada" testNotification: "Notificação teste" @@ -2435,6 +2709,8 @@ _notification: flushNotification: "Limpar notificações" exportOfXCompleted: "Exportação de {x} foi concluída" login: "Alguém entrou na conta" + createToken: "Uma token de acesso foi criada" + createTokenDescription: "Se você não faz ideia, exclua o token de acesso através de \"{text}\"." _types: all: "Todas" note: "Novas notas" @@ -2448,9 +2724,11 @@ _notification: receiveFollowRequest: "Recebeu pedidos de seguidor" followRequestAccepted: "Aceitou pedidos de seguidor" roleAssigned: "Cargo dado" + chatRoomInvitationReceived: "Convite de conversa recebido" achievementEarned: "Conquista desbloqueada" exportCompleted: "A exportação foi concluída" login: "Iniciar sessão" + createToken: "Criar token de acesso" test: "Notificação teste" app: "Notificações de aplicativos conectados" _actions: @@ -2460,6 +2738,9 @@ _notification: _deck: alwaysShowMainColumn: "Sempre mostrar a coluna principal" columnAlign: "Alinhar colunas" + columnGap: "Margem entre colunas" + deckMenuPosition: "Posição do menu do deck" + navbarPosition: "Posição da barra de navegação" addColumn: "Adicionar coluna" newNoteNotificationSettings: "Opções de notificação para novas notas" configureColumn: "Configurar coluna" @@ -2478,6 +2759,7 @@ _deck: useSimpleUiForNonRootPages: "Usar UI simples para páginas navegadas" usedAsMinWidthWhenFlexible: "A largura mínima será usada para isso quando o \"Ajuste automático da largura\" estiver ativado" flexible: "Ajuste automático da largura" + enableSyncBetweenDevicesForProfiles: "Habilitar sincronização das informações do perfil entre dispositivos" _columns: main: "Principal" widgets: "Widgets" @@ -2489,6 +2771,7 @@ _deck: mentions: "Menções" direct: "Notas diretas" roleTimeline: "Linha do tempo do cargo" + chat: "Conversar com usuário" _dialog: charactersExceeded: "Você excedeu o limite de caracteres! Atualmente em {current} de {max}." charactersBelow: "Você está abaixo do limite mínimo de caracteres! Atualmente em {current} of {min}." @@ -2585,6 +2868,8 @@ _moderationLogTypes: deletePage: "Remover página" deleteFlash: "Remover Play" deleteGalleryPost: "Remover a publicação da galeria" + deleteChatRoom: "Sala de Conversas Excluída" + updateProxyAccountDescription: "Atualizar descrição da conta de proxy" _fileViewer: title: "Detalhes do arquivo" type: "Tipo de arquivo" @@ -2592,6 +2877,7 @@ _fileViewer: url: "URL" uploadedAt: "Adicionado em" attachedNotes: "Notas anexadas" + usage: "Usado" thisPageCanBeSeenFromTheAuthor: "Essa página só pode ser vista pelo usuário que enviou esse arquivo." _externalResourceInstaller: title: "Instalar de site externo" @@ -2639,9 +2925,12 @@ _dataSaver: _avatar: title: "Imagem do avatar" description: "Parar animação de avatares. Imagens animadas podem ter um arquivo mais pesado do que imagens normais, potencialmente levando a reduções no tráfego de dados." - _urlPreview: - title: "Miniaturas na prévia de URLs" - description: "Miniaturas na prévia de URLs não serão mais carregadas." + _urlPreviewThumbnail: + title: "Esconder miniaturas em prévias de URL" + description: "Miniaturas em prévias de URL não serão carregadas." + _disableUrlPreview: + title: "Desabilitar prévias de URL" + description: "Desabilita a função de prévias de URL. Diferente das miniaturas, essa função impede o carregamento de toda informação do link." _code: title: "Destaque de código" description: "Se as notações de formatação de código forem utilizadas em MFM, elas não irão carregar até serem selecionadas. Destaque de código exige baixar arquivos de alta definição para cada linguagem de programação. Logo, desabilitar o carregamento automático desses arquivos diminui a quantidade de informação comunicada." @@ -2699,6 +2988,8 @@ _offlineScreen: _urlPreviewSetting: title: "Configurações da prévia de URL" enable: "Habilitar prévia de URL" + allowRedirect: "Permitir redirecionamentos de URL em prévias." + allowRedirectDescription: "Se um URL tem um redirecionamento, você pode habilitar essa função para segui-lo e exibir a prévia do conteúdo redirecionado. Desabilitar isso irá economizar recursos, mas o conteúdo não será exibido." timeout: "Tempo máximo para obter a prévia (ms)" timeoutDescription: "Se demorar mais que esse valor para obter uma prévia, ela não será gerada." maximumContentLength: "Content-Length máximo (em bytes)" @@ -2719,6 +3010,62 @@ _contextMenu: app: "Aplicativo" appWithShift: "Aplicativo com a tecla shift" native: "Nativo" +_gridComponent: + _error: + requiredValue: "Esse valor é necessário" + columnTypeNotSupport: "Validação de expressões regulares (RegEx) só é permitida em colunas type:text." + patternNotMatch: "Esse valor não se encaixa no padrão de {pattern}" + notUnique: "Valor deve ser único" +_roleSelectDialog: + notSelected: "Não selecionado" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copiar linhas selecionadas" + copySelectionRanges: "Copiar seleção" + deleteSelectionRows: "Excluir linhas selecionadas" + deleteSelectionRanges: "Excluir valores selecionados" + searchSettings: "Opções de busca" + searchSettingCaption: "Definir critérios detalhados de busca." + searchLimit: "Limite de busca" + sortOrder: "Ordem de classificação" + registrationLogs: "Histórico de registros" + registrationLogsCaption: "Atualizações e remoções de emoji serão gravadas no histórico. Atualizar, remover, mover a uma nova página ou recarregar limpará o histórico" + alertEmojisRegisterFailedDescription: "Não foi possível atualizar ou remover emojis. Por favor, confira o histórico de registro para mais detalhes." + _logs: + showSuccessLogSwitch: "Exibir sucessos no histórico" + failureLogNothing: "Não há registro de falhas." + logNothing: "Não há registros." + _remote: + selectionRowDetail: "Detalhes da linha selecionada" + importSelectionRows: "Importar linhas selecionadas" + importSelectionRangesRows: "Importar linhas no intervalo" + importEmojisButton: "Importar Emojis selecionados" + confirmImportEmojisTitle: "Importar Emojis" + confirmImportEmojisDescription: "Importar {count} Emoji(s) recebidos de um servidor remoto. Por favor, preste atenção na licença do Emoji. Tem certeza que deseja continuar?" + _local: + tabTitleList: "Emojis registrados" + tabTitleRegister: "Registro de Emoji" + _list: + emojisNothing: "Não há Emojis registrados." + markAsDeleteTargetRows: "Marcar linhas selecionadas para remoção" + markAsDeleteTargetRanges: "Marcar linhas no intervalo para remoção" + alertUpdateEmojisNothingDescription: "Não há Emojis atualizados." + alertDeleteEmojisNothingDescription: "Não há Emojis marcados para remoção." + confirmMovePage: "Deseja mudar de página?" + confirmChangeView: "Deseja mudar de seção?" + confirmUpdateEmojisDescription: "Atualizando {count} Emoji(s). Deseja continuar?" + confirmDeleteEmojisDescription: "Removendo {count} Emoji(s) marcado(s). Deseja continuar?" + confirmResetDescription: "Todas as mudanças serão redefinidas." + confirmMovePageDesciption: "Mudanças foram feitas nos Emojis dessa página. Se você sair sem salvar, todas serão descartadas." + dialogSelectRoleTitle: "Buscar por cargo que pode usar esse Emoji" + _register: + uploadSettingTitle: "Configurações de envio" + uploadSettingDescription: "Nessa tela, você pode configurar o comportamento ao enviar Emojis." + directoryToCategoryLabel: "Transformar as pastas em categorias" + directoryToCategoryCaption: "Quando você arrastar um diretório, converter o caminho das pastas no campo \"categoria\"." + confirmRegisterEmojisDescription: "Registrando os Emojis da lista como novos Emojis personalizados. Deseja continuar? (Para evitar sobrecarga, apenas {count} Emoji(s) podem ser registrados em uma única operação)" + confirmClearEmojisDescription: "Descartando edições e limpando Emojis da lista. Deseja continuar?" + confirmUploadEmojisDescription: "Enviando {count} arquivo(s) arrastados ao drive. Deseja continuar?" _embedCodeGen: title: "Personalizar código do embed" header: "Exibir cabeçalho" @@ -2758,7 +3105,227 @@ _remoteLookupErrors: _noSuchObject: title: "Não encontrado" description: "O recurso solicitado não foi encontrado, confira o endereço." +_captcha: + verify: "Por favor, verifique o CAPTCHA" + testSiteKeyMessage: "Você pode conferir a prévia inserindo valores de teste para o site e chaves secretas.\nVeja a página seguinte para mais detalhes." + _error: + _requestFailed: + title: "O pedido do CAPTCHA falhou" + text: "Por favor, tente novamente ou verifique as configurações." + _verificationFailed: + title: "A validação do CAPTCHA falhou" + text: "Por favor, verifique se as configurações estão corretas." + _unknown: + title: "Erro CAPTCHA" + text: "Houve um erro inexperado." +_bootErrors: + title: "Falha ao carregar" + serverError: "Se o problema persistir após esperar um momento e recarregar, contate a administração da instância com o seguinte ID de erro." + solution: "O seguinte pode resolver o problema." + solution1: "Atualize seu navegador e sistema operacional para a última versão." + solution2: "Desative o bloqueador de anúncios" + solution3: "Limpe o cache do navegador" + solution4: "Defina dom.webaudio.enabled como verdadeiro no Navegador Tor" + otherOption: "Outras opções" + otherOption1: "Excluir ajustes de cliente e cache" + otherOption2: "Iniciar o cliente simples" + otherOption3: "Iniciar ferramenta de reparo" + otherOption4: "Abrir Misskey no modo seguro" _search: searchScopeAll: "Todos" searchScopeLocal: "Local" + searchScopeServer: "Servidor específico" searchScopeUser: "Usuário específico" + pleaseEnterServerHost: "Insira o endereço do servidor" + pleaseSelectUser: "Selecione um usuário" + serverHostPlaceholder: "Exemplo: misskey.example.com" +_serverSetupWizard: + installCompleted: "Instalação do Misskey concluída!" + firstCreateAccount: "Para iniciar, crie uma conta de administrador." + accountCreated: "Conta de administrador foi criada!" + serverSetting: "Configurações de Servidor" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "O assistente facilita a configuração do servidor." + settingsYouMakeHereCanBeChangedLater: "Configurações alteradas pelo assistente podem ser ajustadas posteriormente." + howWillYouUseMisskey: "Como você usará o Misskey?" + _use: + single: "Servidor de Usuário Único" + single_description: "Utilizar servidor sozinho." + single_youCanCreateMultipleAccounts: "Múltiplas contas podem ser criadas se necessário, mesmo operando como servidor de usuário único." + group: "Servidor de Grupo" + group_description: "Convide outros usuários confiáveis para utilizar com mais de um usuário" + open: "Servidor Público" + open_description: "Permitir registro de todos." + openServerAdvice: "Aceitar um número alto de pessoas desconhecidas pode envolve um risco. Recomendamos que você opere com um sistema de moderação confiável para resolver quaisquer problemas." + openServerAntiSpamAdvice: "Para prevenir que o seu servidor se torne alvo de spam, é essencial cuidar da segurança habilitando recursos antibot como o reCAPTCHA." + howManyUsersDoYouExpect: "Quantos usuários você espera?" + _scale: + small: "Menos que 100 (pequeno porte)" + medium: "Entre 100 e 1000 usuários (médio porte)" + large: "Mais que 1000 usuários (larga escala)" + largeScaleServerAdvice: "Servidores de larga escala podem precisar de conhecimento avançado de infraestrutura, como balanceamento de carga e replicação de banco de dados." + doYouConnectToFediverse: "Você deseja conectar-se com o Fediverso?" + doYouConnectToFediverse_description1: "Quando conectado com uma rede distribuída de servidores (Fediverso), o conteúdo pode ser trocado com outros servidores." + doYouConnectToFediverse_description2: "Conectar com o Fediverso também é chamado de \"federação\"" + youCanConfigureMoreFederationSettingsLater: "Configurações adicionais como especificar servidores para conectar-se com podem ser feitas posteriormente" + remoteContentsCleaning: "Limpeza automática de conteúdos recebidos" + remoteContentsCleaning_description: "A federação pode resultar em uma entrada contínua de conteúdo. Habilitar a limpeza automática removerá conteúdo obsoleto e não referenciado do servidor para economizar armazenamento." + adminInfo: "Informações da administração" + adminInfo_description: "Define as informações do administrador usadas para receber consultas." + adminInfo_mustBeFilled: "Deve ser preenchido se o servidor é público ou se a federação está ativa." + followingSettingsAreRecommended: "As configurações a seguir são recomendadas" + applyTheseSettings: "Aplicar essas configurações" + skipSettings: "Pular configuração" + settingsCompleted: "Instalação concluída!" + settingsCompleted_description: "Obrigado pelo seu tempo. Agora que tudo está pronto, você pode começar a utilizar o servidor." + settingsCompleted_description2: "As configurações do servidor podem ser alteradas no \"Painel de Controle\"" + donationRequest: "Solicitação de Doação" + _donationRequest: + text1: "Misskey é software aberto desenvolvido por voluntários." + text2: "Nós apreciaríamos o seu apoio para podermos continuar o desenvolvimento desse software no futuro." + text3: "Também há benefícios especiais para apoiadores!" +_uploader: + editImage: "Editar Imagem" + compressedToX: "Comprimido para {x}" + savedXPercent: "Salvando {x}%" + abortConfirm: "Alguns arquivos não foram enviados, deseja abortar?" + doneConfirm: "Alguns arquivos não foram enviados, deseja continuar mesmo assim?" + maxFileSizeIsX: "O tamanho máximo de arquivos enviados é {x}" + allowedTypes: "Tipos de arquivo enviáveis" + tip: "O arquivo não foi enviado. Então, esse diálogo permite que você confirme, renomeie, comprima e recorte o arquivo antes de enviar. Quando estiver pronto, você pode enviar apertando o botão \"Enviar\"." +_clientPerformanceIssueTip: + title: "Dicas de desempenho" + makeSureDisabledAdBlocker: "Desative o seu bloqueador de anúncios" + makeSureDisabledAdBlocker_description: "Bloqueadores de anúncios podem afetar o desempenho. Certifique-se que eles não estão habilitados no seu sistema ou nos recursos/extensões do navegador. " + makeSureDisabledCustomCss: "Desabilite CSS personalizado" + makeSureDisabledCustomCss_description: "Substituir o estilo da página pode afetar o desempenho. Certifique-se que o CSS personalizado ou extensões que modifiquem o estilo da página estejam desabilitados." + makeSureDisabledAddons: "Desabilite extensões" + makeSureDisabledAddons_description: "Algumas extensões podem afetar comportamentos do cliente e afetar o desempenho. Por favor, desative as extensões do seu navegador e veja se isso melhora a situação." +_clip: + tip: "Clip é uma função que permite organização das suas notas." +_userLists: + tip: "Listas podem conter qualquer usuário que você especificar em sua criação. A lista criada aparece como uma linha do tempo exibindo usuários selecionados." +watermark: "Marca d'água" +defaultPreset: "Predefinição Padrão" +_watermarkEditor: + tip: "Uma marca d'água, como informação de autoria, pode ser adicionada à imagem." + quitWithoutSaveConfirm: "Descartar mudanças?" + driveFileTypeWarn: "Esse arquivo não é compatível" + driveFileTypeWarnDescription: "Escolha um arquivo de imagem" + title: "Editar marca d'água" + cover: "Cobrir tudo" + repeat: "Espalhar pelo conteúdo" + opacity: "Opacidade" + scale: "Tamanho" + text: "Texto" + qr: "Código QR" + position: "Posição" + margin: "Margem" + type: "Tipo" + image: "imagem" + advanced: "Avançado" + angle: "Ângulo" + stripe: "Listras" + stripeWidth: "Largura da linha" + stripeFrequency: "Número de linhas" + polkadot: "Bolinhas" + checker: "Xadrez" + polkadotMainDotOpacity: "Opacidade da bolinha principal" + polkadotMainDotRadius: "Raio da bolinha principal" + polkadotSubDotOpacity: "Opacidade da bolinha secundária" + polkadotSubDotRadius: "Raio das bolinhas adicionais" + polkadotSubDotDivisions: "Número de bolinhas adicionais" + leaveBlankToAccountUrl: "Deixe em branco para utilizar URL da conta" +_imageEffector: + title: "Efeitos" + addEffect: "Adicionar efeitos" + discardChangesConfirm: "Tem certeza que deseja sair? Há mudanças não salvas." + _fxs: + chromaticAberration: "Aberração cromática" + glitch: "Glitch" + mirror: "Espelho" + invert: "Inverter Cores" + grayscale: "Tons de Cinza" + blur: "Desfoque" + pixelate: "Pixelizar" + colorAdjust: "Correção de Cores" + colorClamp: "Compressão de Cores" + colorClampAdvanced: "Compressão Avançada de Cores" + distort: "Distorção" + threshold: "Limiarização Binária" + zoomLines: "Linhas de Ação" + stripe: "Listras" + polkadot: "Bolinhas" + checker: "Xadrez" + blockNoise: "Bloquear Ruído" + tearing: "Descontinuidade" + fill: "Preencher" + _fxProps: + angle: "Ângulo" + scale: "Tamanho" + size: "Tamanho" + radius: "Raio" + samples: "Número de amostras" + offset: "Posição" + color: "Cor" + opacity: "Opacidade" + normalize: "Normalizar" + amount: "Quantidade" + lightness: "Esclarecer" + contrast: "Contraste" + hue: "Matiz" + brightness: "Brilho" + saturation: "Saturação" + max: "Máximo" + min: "Mínimo" + direction: "Direção" + phase: "Fase" + frequency: "Frequência" + strength: "Força" + glitchChannelShift: "Mudança de canal" + seed: "Valor da semente" + redComponent: "Componente vermelho" + greenComponent: "Componente verde" + blueComponent: "Componente azul" + threshold: "Limiar" + centerX: "Centralizar X" + centerY: "Centralizar Y" + zoomLinesSmoothing: "Suavização" + zoomLinesSmoothingDescription: "Suavização e largura das linhas de zoom não podem ser utilizados simultaneamente." + zoomLinesThreshold: "Largura das linhas de zoom" + zoomLinesMaskSize: "Diâmetro do centro" + zoomLinesBlack: "Linhas pretas" + circle: "Circular" +drafts: "Rascunhos" +_drafts: + select: "Selecionar Rascunho" + cannotCreateDraftAnymore: "O número máximo de rascunhos foi excedido." + cannotCreateDraft: "Você não pode criar um rascunho com esse conteúdo." + delete: "Excluir Rascunho" + deleteAreYouSure: "Excluir rascunho?" + noDrafts: "Sem rascunhos" + replyTo: "Resposta a {user}" + quoteOf: "Citação à nota de {user}" + postTo: "Publicando em {channel}" + saveToDraft: "Salvar como Rascunho" + restoreFromDraft: "Restaurar de Rascunho" + restore: "Redefinir" + listDrafts: "Lista de Rascunhos" + schedule: "Agendar nota" + listScheduledNotes: "Lista de notas agendadas" + cancelSchedule: "Cancelar agendamento" +qr: "Código QR" +_qr: + showTabTitle: "Visualizar" + readTabTitle: "Escanear" + shareTitle: "{name} {acct}" + shareText: "Siga-me no Fediverso!" + chooseCamera: "Escolher câmera" + cannotToggleFlash: "Não foi possível ligar a lanterna" + turnOnFlash: "Ligar a lanterna" + turnOffFlash: "Desligar a lanterna" + startQr: "Retornar ao leitor de códigos QR" + stopQr: "Deixar o leitor de códigos QR" + noQrCodeFound: "Nenhum código QR encontrado" + scanFile: "Escanear imagem de dispositivo" + raw: "Texto" + mfm: "MFM" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 5e0d3f221f..bf352b45c2 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -1,15 +1,19 @@ --- _lang_: "Română" headlineMisskey: "O rețea conectată prin note" -introMisskey: "Bine ai venit! Misskey este un serviciu de microblogging open source și decentralizat.\nCreează \"note\" cu care să îți poți împărți gândurile cu oricine din jurul tău. 📡\nCu \"reacții\" îți poți expirma rapid părerea despre notele oricui. 👍\nHai să explorăm o lume nouă! 🚀" +introMisskey: "Bine ai venit! Misskey este un serviciu de microblogging open source și decentralizat.\nCreează \"note\" cu care să îți poți împărțasi gândurile cu oricine din jurul tău. 📡\nCu \"reacții\" îți poți exprima rapid părerea despre notele oricui. 👍\nHai să explorăm o lume nouă! 🚀" poweredByMisskeyDescription: "{name} este unul dintre serviciile care se folosește de platforma open source Misskey." monthAndDay: "{day}/{month}" search: "Caută" +reset: "Resetează." notifications: "Notificări" username: "Nume de utilizator" password: "Parolă" +initialPasswordForSetup: "Parola pentru a începe configurarea inițială." +initialPasswordIsIncorrect: "Parola inițială este incorectă." +initialPasswordForSetupDescription: "Dacă ai instalat singur Misskey, utilizează parola pe care ai introdus-o în fișierul de configurare.\n\nDacă utilizezi un serviciu de găzduire(hosting) precum Misskey, te rugăm să utilizezi parola furnizată.\n\nDacă nu ai setat o parolă, las-o necompletată și mergi mai departe." forgotPassword: "Am uitat parola" -fetchingAsApObject: "Se aduce din Fediverse..." +fetchingAsApObject: "Se preia din Fediverse..." ok: "OK" gotIt: "Am înțeles!" cancel: "Anulează" @@ -45,26 +49,28 @@ pin: "Fixează pe profil" unpin: "Anulati fixare" copyContent: "Copiază conținutul" copyLink: "Copiază link-ul" -copyLinkRenote: "Copiază linkul pentru renote" +copyRemoteLink: "Copiază sursa externă." +copyLinkRenote: "Copiază linkul pentru re-notare" delete: "Şterge" deleteAndEdit: "Șterge și editează" -deleteAndEditConfirm: "Ești sigur că vrei să ștergi această notă și să o editezi? Vei pierde reacțiile, re-notele și răspunsurile acesteia." +deleteAndEditConfirm: "Ești sigur(ă) că vrei să ștergi această notă și să o editezi? Vei pierde reacțiile, Re-Notele și răspunsurile acestora." addToList: "Adaugă în listă" addToAntenna: "Adaugă la antenă" sendMessage: "Trimite un mesaj" copyRSS: "Copiază RSS" copyUsername: "Copiază numele de utilizator" -copyUserId: "Copiază numele de utilizator" +copyUserId: "Copiază ID-ul de utilizator" copyNoteId: "Copiază ID-ul notiței" copyFileId: "Copiază ID-ul fișierului" copyFolderId: "Copiază ID-ul folderului" -copyProfileUrl: "Copiază URL profil" +copyProfileUrl: "Copiază URL-ul profilului " searchUser: "Caută un utilizator" +searchThisUsersNotes: "Caută în notele acestui utilizator." reply: "Răspunde" loadMore: "Incarcă mai mult" showMore: "Arată mai mult" showLess: "Închide" -youGotNewFollower: "te-a urmărit" +youGotNewFollower: "Te-a urmărit" receiveFollowRequest: "Cerere de urmărire primită" followRequestAccepted: "Cerere de urmărire acceptată" mention: "Mențiune" @@ -75,21 +81,21 @@ import: "Importă" export: "Exportă" files: "Fișiere" download: "Descarcă" -driveFileDeleteConfirm: "Ești sigur ca vrei să ștergi fișierul \"{name}\"? Notele atașate fișierului vor fi șterse și ele." -unfollowConfirm: "Ești sigur ca vrei să nu mai urmărești pe {name}?" +driveFileDeleteConfirm: "Ești sigur(ă) că vrei să ștergi fișierul \"{name}\"? Notele atașate fișierului vor fi și ele șterse." +unfollowConfirm: "Ești sigur(ă) că vrei să nu mai urmărești pe {name}?" exportRequested: "Ai cerut un export. S-ar putea să ia un pic. Va fi adăugat in Drive-ul tău odată completat." importRequested: "Ai cerut un import. S-ar putea să ia un pic." lists: "Liste" -noLists: "Nu ai nici o listă" +noLists: "Nu ai nicio listă" note: "Notă" notes: "Note" -following: "Urmărești" +following: "Îl urmărești" followers: "Urmăritori" followsYou: "Te urmărește" createList: "Creează listă" manageLists: "Gestionează listele" error: "Eroare" -somethingHappened: "A survenit o eroare" +somethingHappened: "A apărut o eroare" retry: "Reîncearcă" pageLoadError: "A apărut o eroare la încărcarea paginii." pageLoadErrorDescription: "De obicei asta este cauzat de o eroare de rețea sau cache-ul browser-ului. Încearcă să cureți cache-ul și apoi să încerci din nou puțin mai târziu." @@ -99,20 +105,23 @@ enterListName: "Introdu un nume pentru listă" privacy: "Confidenţialitate" makeFollowManuallyApprove: "Fă cererile de urmărire să necesite aprobare" defaultNoteVisibility: "Vizibilitate implicită" -follow: "Urmărești" +follow: "Urmărește" followRequest: "Trimite cerere de urmărire" followRequests: "Cereri de urmărire" unfollow: "Nu mai urmări" followRequestPending: "Cerere de urmărire în așteptare" enterEmoji: "Introdu un emoji" -renote: "Re-notează" -unrenote: "Ia înapoi re-nota" +renote: "Re-Notează" +unrenote: "Anulează re-nota" renoted: "Re-notat." +renotedToX: "Re-notă către {name}." cantRenote: "Această postare nu poate fi re-notată." cantReRenote: "O re-notă nu poate fi re-notată." quote: "Citează" -inChannelRenote: "Renotează în canal" +inChannelRenote: "Re-Notează în canal" inChannelQuote: "Citează în canal" +renoteToChannel: "Re-notă către alte canale." +renoteToOtherChannel: "Re-notă către alte canale." pinnedNote: "Notă fixată" pinned: "Fixat pe profil" you: "Tu" @@ -121,42 +130,52 @@ sensitive: "NSFW" add: "Adaugă" reaction: "Reacție" reactions: "Reacție" +emojiPicker: "Selectator de emoji" +pinnedEmojisForReactionSettingDescription: "Poți seta emoji-urile să fie fixate atunci când reacționați." +pinnedEmojisSettingDescription: "Poți seta emoji-urile să fie fixate și afișate la introducerea emoji-urilor." +emojiPickerDisplay: "Meniu de selectare ale reacțiilor." +overwriteFromPinnedEmojisForReaction: "Ignoră din setările de reacție." +overwriteFromPinnedEmojis: "Ignoră din setările generale." reactionSettingDescription2: "Trage pentru a rearanja, apasă pe \"+\" pentru a adăuga." rememberNoteVisibility: "Amintește setarea de vizibilitate a notelor" attachCancel: "Înlătură atașament" +deleteFile: "Șterge fișierul." markAsSensitive: "Marchează ca NSFW" unmarkAsSensitive: "Demarchează ca NSFW" -enterFileName: "Introduceţi numele fişierului" +enterFileName: "Introdu numele fişierului" mute: "Amuțește" unmute: "Înlătură amuțirea" -renoteMute: "Renotări pe modul silențios" +renoteMute: "Re-notări pe modul silențios" renoteUnmute: "Scoate renotările de pe modul silențios" block: "Blochează" unblock: "Deblochează" suspend: "Suspendă" unsuspend: "Anulează suspendare" -blockConfirm: "Ești sigur că vrei să blochezi acest cont?" -unblockConfirm: "Ești sigur ca vrei să deblochezi acest cont?" -suspendConfirm: "Ești sigur ca vrei să suspendezi acest cont?" -unsuspendConfirm: "Ești sigur ca vrei să nu mai suspendezi acest cont?" +blockConfirm: "Ești sigur(ă) că vrei să blochezi acest cont?" +unblockConfirm: "Ești sigur(ă) că vrei să deblochezi acest cont?" +suspendConfirm: "Ești sigur(ă) că vrei să suspendezi acest cont?" +unsuspendConfirm: "Ești sigur că vrei să nu mai suspendezi acest cont?" selectList: "Selectează o listă" -editList: "Editați lista" -selectChannel: "Selectaţi canalul" +editList: "Editează lista" +selectChannel: "Selectează canalul" selectAntenna: "Selectează o antenă" editAntenna: "Editează antena" -selectWidget: "Selectați un widget" +createAntenna: "Creează o antenă." +selectWidget: "Alege un widget" editWidgets: "Editează widget-urile" editWidgetsExit: "Terminat" -customEmojis: "Emoji personalizat" +customEmojis: "Emoji personalizate" emoji: "Emoji" emojis: "Emoji-uri" emojiName: "Numele emoji-ului" emojiUrl: "URL-ul emoji-ului" addEmoji: "Adaugă un emoji" settingGuide: "Setări recomandate" -cacheRemoteFiles: "Ține fișierele externe in cache" -cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului de stocare, dar va crește traficul, deoarece thumbnail-urile nu vor fi generate." +cacheRemoteFiles: "Reţine fișierele externe in memoria cache." +cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului de stocare, dar va crește traficul, deoarece miniaturile nu vor fi generate." youCanCleanRemoteFilesCache: "Poți goli cache-ul prin a apăsa pe butonul de 🗑️ din fereastra de gestionare a fișierelor." +cacheRemoteSensitiveFiles: "Memorează în cache fișierele sensibile la distanță." +cacheRemoteSensitiveFilesDescription: "Dacă dezactivezi această setare, fișierele sensibile externe vor fi conectate direct și nu stocate în cache." flagAsBot: "Marchează acest cont ca bot" flagAsBotDescription: "Activează această opțiune dacă acest cont este controlat de un program. Daca e activată, aceasta va juca rolul unui indicator pentru dezvoltatori pentru a preveni interacțiunea în lanțuri infinite cu ceilalți boți și ajustează sistemele interne al Misskey pentru a trata acest cont drept un bot." flagAsCat: "Marchează acest cont ca pisică" @@ -165,18 +184,24 @@ flagShowTimelineReplies: "Arată răspunsurile în cronologie" flagShowTimelineRepliesDescription: "Dacă e activată vor fi arătate în cronologie răspunsurile utilizatorilor către alte notele altor utilizatori." autoAcceptFollowed: "Aprobă automat cererile de urmărire de la utilizatorii pe care îi urmărești" addAccount: "Adaugă un cont" +reloadAccountsList: "Reîncarcă informațiile din lista de conturi" loginFailed: "Autentificare eșuată" showOnRemote: "Vezi mai multe pe instanța externă" +continueOnRemote: "Continuă de pe sursa externa." +chooseServerOnMisskeyHub: "Selectează un server din Hub-ul Misskey." +specifyServerHost: "Specifică un server gazdă(host)." +inputHostName: "Introdu numele gazdă(hostname)." general: "General" wallpaper: "Imagine de fundal" -setWallpaper: "Setați imaginea de fundal" +setWallpaper: "Setează imaginea de fundal" removeWallpaper: "Șterge imagine de fundal" searchWith: "Caută: {q}" youHaveNoLists: "Nu ai nici o listă" -followConfirm: "Ești sigur ca vrei să urmărești pe {name}?" +followConfirm: "Ești sigur(ă) că vrei să urmărești pe {name}?" proxyAccount: "Cont proxy" proxyAccountDescription: "Un cont proxy este un cont care se comportă ca un urmăritor extern pentru utilizatorii puși sub anumite condiții. De exemplu, când un cineva adaugă un utilizator extern intr-o listă, activitatea utilizatorului extern nu va fi adusă în instanță daca nici un utilizator local nu urmărește acel utilizator, așa că în schimb contul proxy îl va urmări." host: "Gazdă" +selectSelf: "Selectează-te pe tine însuți." selectUser: "Selectează un utilizator" recipient: "Destinatar" annotation: "Adnotări" @@ -191,6 +216,8 @@ perHour: "Pe oră" perDay: "Pe zi" stopActivityDelivery: "Nu mai trimite activități" blockThisInstance: "Blochează această instanță" +silenceThisInstance: "Ascunde acest server." +mediaSilenceThisInstance: "Ascunde conținutul media din acest server." operations: "Operațiuni" software: "Software" version: "Versiune" @@ -204,24 +231,30 @@ disk: "Disk" instanceInfo: "Informații despre instanță" statistics: "Statistici" clearQueue: "Șterge coada" -clearQueueConfirmTitle: "Ești sigur că vrei să cureți coada?" +clearQueueConfirmTitle: "Ești sigur(ă) că vrei să cureți coada?" clearQueueConfirmText: "Orice notă rămasă în coadă nu va fi federată. De obicei această operație nu este necesară." clearCachedFiles: "Golește cache-ul" -clearCachedFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele externe din cache?" +clearCachedFilesConfirm: "Ești sigur(ă) că vrei să ștergi toate fișierele externe din cache?" blockedInstances: "Instanțe blocate" -blockedInstancesDescription: "Scrie hostname-urile instanțelor pe care dorești să le blochezi. Instanțele listate nu vor mai putea să comunice cu această instanță." +blockedInstancesDescription: "Scrie numele gazdă(hostname) ale serverelor pe care dorești să le blochezi. Serverele listate nu vor mai putea să comunice cu acest server." +silencedInstances: "Servere ascunse." +silencedInstancesDescription: "Listează numele de gazdă(hostname) ale serverelor pe care dorești să le ascunzi, separate printr-o nouă linie de spațiere. Toate conturile care aparțin serverelor enumerate vor fi tratate ca fiind ascunse și pot face doar solicitări de urmărire și nu pot menționa conturi locale dacă nu sunt urmate. Acest lucru nu va afecta serverele blocate." +mediaSilencedInstances: "Servere cu conținutul media ascuns." +mediaSilencedInstancesDescription: "Setați numele de gazdă(hostname-urile) ale serverelor pe care dorești să le ascunzi, separate de o linie noua de spațiere. Orice fișier din conturile de pe un server cu sunet media vor fi tratate ca fiind sensibile și nu vor putea folosi emoji-uri personalizate. Nu are niciun efect asupra serverelor blocate." +federationAllowedHosts: "Servere permise pentru federare" +federationAllowedHostsDescription: "Specifica numele de gazdă ale serverelor pe care dorești să le permiți federarea, separate prin spații noi." muteAndBlock: "Amuțiri și Blocări" mutedUsers: "Utilizatori amuțiți" blockedUsers: "Utilizatori blocați" noUsers: "Niciun utilizator" editProfile: "Editează profilul" -noteDeleteConfirm: "Ești sigur că vrei să ștergi această notă?" +noteDeleteConfirm: "Ești sigur(ă) că vrei să ștergi această notă?" pinLimitExceeded: "Nu poți mai fixa mai multe note" -intro: "Misskey s-a instalat! Te rog crează un utilizator admin." done: "Gata" processing: "Se procesează" preview: "Previzualizare" default: "Prestabilit" +defaultValueIs: "Valori implicite: {value}" noCustomEmojis: "Nu e niciun emoji" noJobs: "Nu e niciun job" federating: "Federație" @@ -232,7 +265,7 @@ subscribing: "Abonare" publishing: "Publicare" notResponding: "Nu răspunde" instanceFollowing: "Urmărind în instanță" -instanceFollowers: "Urmăritori ai instanței" +instanceFollowers: "Urmăritori al instanței" instanceUsers: "Utilizatori ai acestei instanțe" changePassword: "Schimbă parolă" security: "Securitate" @@ -250,9 +283,10 @@ announcements: "Anunțuri" imageUrl: "URL-ul imaginii" remove: "Şterge" removed: "Șterș cu succes" -removeAreYouSure: "Ești sigur că vrei să înlături {x}?" -deleteAreYouSure: "Ești sigur că vrei să ștergi {x}?" +removeAreYouSure: "Ești sigur(ă) că vrei să înlături {x}?" +deleteAreYouSure: "Ești sigur(ă) că vrei să ștergi {x}?" resetAreYouSure: "Sigur vrei să resetezi?" +areYouSure: "Ești sigur(ă)?" saved: "Salvat" upload: "Încarcă" keepOriginalUploading: "Păstrează imaginea originală" @@ -266,8 +300,13 @@ uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează înc explore: "Explorează" messageRead: "Citit" noMoreHistory: "Nu există mai mult istoric" +startChat: "Pornește chat-ul" nUsersRead: "citit de {n}" agreeTo: "Sunt de acord cu {0}" +agree: "De acord" +agreeBelow: "Sunt de acord cu cele menționate mai jos" +basicNotesBeforeCreateAccount: "Detalii importante" +termsOfService: "Termenii serviciului" start: "Să începem" home: "Acasă" remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația afișată poate fi incompletă." @@ -288,21 +327,24 @@ darkThemes: "Teme întunecate" syncDeviceDarkMode: "Sincronizează Modul Întunecat cu setările dispozitivului" drive: "Drive" fileName: "Nume fișier" -selectFile: "Alege un fisier" +selectFile: "Alege un fișier" selectFiles: "Alege fișiere" selectFolder: "Selectează un folder" selectFolders: "Selectează folderele" +fileNotSelected: "Niciun fișier selectat" renameFile: "Redenumește fișier" folderName: "Nume folder" createFolder: "Crează folder" renameFolder: "Redenumește acest folder" deleteFolder: "Șterge acest folder" -addFile: "Adăugați un fișier" +folder: "Folder" +addFile: "Adaugă un fișier" +showFile: "Arata fișierele" emptyDrive: "Drive-ul tău e gol" emptyFolder: "Folder-ul acesta este gol" unableToDelete: "Nu se poate șterge" inputNewFileName: "Introdu un nou nume de fișier" -inputNewDescription: "Introdu o descriere nouă" +inputNewDescription: "Introdu o titrare nouă" inputNewFolderName: "Introdu un nume de folder nou" circularReferenceFolder: "Destinația folderului este un subfolder al folderului pe care dorești să îl muți." hasChildFilesOrFolders: "Acest folder nu este gol, așa că nu poate fi șters." @@ -310,8 +352,9 @@ copyUrl: "Copiază URL" rename: "Redenumește" avatar: "Avatar" banner: "Banner" +displayOfSensitiveMedia: "Afișarea conținutului media sensibil" whenServerDisconnected: "Când pierzi conexiunea cu serverul" -disconnectedFromServer: "Conecțiunea cu serverul a fost pierdută" +disconnectedFromServer: "Conexiunea cu serverul a fost pierdută" reload: "Reîncarcă" doNothing: "Ignoră" reloadConfirm: "Ai dori să reîmprospătezi cronologia?" @@ -347,21 +390,26 @@ bannerUrl: "URL-ul imaginii de banner" backgroundImageUrl: "URL-ul imaginii de fundal" basicInfo: "Informații de bază" pinnedUsers: "Utilizatori fixați" -pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor fi fixați pe pagina \"Explorează\"." +pinnedUsersDescription: "Scrie utilizatorii, separați prin o linie de rând, care vor fi fixați pe pagina \"Explorează\"." pinnedPages: "Pagini fixate" -pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vâruful paginii acestei instanțe, separate de pauze de rând." +pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vârful paginii acestei instanțe, separate de o linie de spațiere." pinnedClipId: "ID-ul clip-ului pe care să îl fixezi" pinnedNotes: "Notă fixată" hcaptcha: "hCaptcha" enableHcaptcha: "Activează hCaptcha" hcaptchaSiteKey: "Site key" hcaptchaSecretKey: "Secret key" +mcaptcha: "mCaptcha" +enableMcaptcha: "Permite mCaptcha" mcaptchaSiteKey: "Site key" mcaptchaSecretKey: "Secret key" +mcaptchaInstanceUrl: "URL-ul serverului mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Activează reCAPTCHA" recaptchaSiteKey: "Site key" recaptchaSecretKey: "Secret key" +turnstile: "\nTurnstile" +enableTurnstile: "Permite Turnstile" turnstileSiteKey: "Site key" turnstileSecretKey: "Secret key" avoidMultiCaptchaConfirm: "Folosirea mai multor sisteme Captcha poate cauza interferență între acestea. Ai dori să dezactivezi alte sisteme Captcha acum active? Dacă preferi să rămână activate, apasă Anulare." @@ -371,9 +419,11 @@ name: "Nume" antennaSource: "Sursa antenei" antennaKeywords: "Cuvinte cheie ascultate" antennaExcludeKeywords: "Cuvinte cheie excluse" -antennaKeywordsDescription: "Separă cu spații pentru o condiție ȘI sau cu o întrerupere de rând pentru o condiție SAU." +antennaExcludeBots: "Exclude conturi tip bot" +antennaKeywordsDescription: "Separă cu spații pentru o condiție ''AND'' sau cu o linie de spațiere nouă pentru o condiție ''OR''." notifyAntenna: "Notifică-mă pentru note noi" withFileAntenna: "Doar note cu fișiere" +excludeNotesInSensitiveChannel: "Exclude note din canale sensibile" enableServiceworker: "Activează ServiceWorker" antennaUsersDescription: "Scrie un nume de utilizator per linie" caseSensitive: "Sensibil la majuscule și minuscule" @@ -382,13 +432,13 @@ connectedTo: "Următoarele conturi sunt conectate" notesAndReplies: "Note și răspunsuri" withFiles: "Incluzând fișiere" silence: "Amuțește" -silenceConfirm: "Ești sigur că vrei să amuțești acest utilizator?" +silenceConfirm: "Ești sigur(ă) că vrei să amuțești acest utilizator?" unsilence: "Anulează amuțirea" -unsilenceConfirm: "Ești sigur că vrei să anulezi amuțirea acestui utilizator?" +unsilenceConfirm: "Ești sigur(ă) că vrei să anulezi amuțirea acestui utilizator?" popularUsers: "Utilizatori populari" recentlyUpdatedUsers: "Utilizatori activi recent" recentlyRegisteredUsers: "Utilizatori ce s-au alăturat recent" -recentlyDiscoveredUsers: "Utilizatori descoperiți recent" +recentlyDiscoveredUsers: "Utilizatori recent descoperiți" exploreUsersCount: "Aici sunt {count} utilizatori" exploreFediverse: "Explorează Fediverse-ul" popularTags: "Taguri populare" @@ -397,12 +447,24 @@ about: "Despre" aboutMisskey: "Despre Misskey" administrator: "Administrator" token: "Token" +2fa: "Autentificare cu doi factori" +setupOf2fa: "Configurează autentificarea cu doi factori" +totp: "Aplicația de autentificare" +totpDescription: "Folosește o aplicație de autentificare pentru a putea utiliza parole de unica folosință" moderator: "Moderator" +moderation: "Moderare" +moderationNote: "Note de moderare" +moderationNoteDescription: "Poți completa note care vor fi partajate doar între moderatori." +addModerationNote: "Adaugă o notă de moderare" +moderationLogs: "Jurnal de moderare" nUsersMentioned: "Menționat de {n} utilizatori" +securityKeyAndPasskey: "Cheie de securitate - cheie de acces " securityKey: "Cheie de securitate" lastUsed: "Ultima utilizată" +lastUsedAt: "Ultima utilizare: {t}" unregister: "Dezînregistrează" passwordLessLogin: "Autentificare fără parolă" +passwordLessLoginDescription: "Permite autentificare fără parolă folosind doar o cheie de securitate sau o cheie de acces" resetPassword: "Resetează parola" newPasswordIs: "Noua parolă este \"{password}\"" reduceUiAnimation: "Redu animațiile interfeței" @@ -427,8 +489,10 @@ retype: "Introdu din nou" noteOf: "Notă de {user}" quoteAttached: "Citat" quoteQuestion: "Vrei să adaugi ca citat?" +attachAsFileQuestion: "Textul clipboard-ului este lung. Dorești să-l atașezi ca fișier text?" onlyOneFileCanBeAttached: "Poți atașa un singur fișier la un mesaj" signinRequired: "Te rog autentifică-te" +signinOrContinueOnRemote: "Pentru a continua, trebuie să mergi la serverul dvs. sau să te înregistrezi și să te conectezi la acest server." invitations: "Invită" invitationCode: "Cod de invitație" checking: "Se verifică..." @@ -443,13 +507,23 @@ strongPassword: "Parolă puternică" passwordMatched: "Se potrivește!" passwordNotMatched: "Nu se potrivește" signinWith: "Autentifică-te cu {x}" -signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse sunt incorecte." +signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introdusă e incorectă." or: "Sau" language: "Limbă" uiLanguage: "Limba interfeței" aboutX: "Despre {x}" +emojiStyle: "Stil emoji" +native: "Nativ" +menuStyle: "Stilul meniului" +style: "Stil" +drawer: "Sertar" +popup: "Pop up" +showNoteActionsOnlyHover: "Afișează acțiunile de notare numai la trecerea cursorului" +showReactionsCount: "Afișează numărul de reacții la note" noHistory: "Nu există istoric" signinHistory: "Istoric autentificări" +enableAdvancedMfm: "Permite autentificarea multiplă(MFM) avansată" +enableAnimatedMfm: "Permite autentificarea multiplă(MFM) animată" doing: "Se procesează..." category: "Categorie" tags: "Etichete" @@ -458,6 +532,8 @@ createAccount: "Creează un cont" existingAccount: "Cont existent" regenerate: "Regenerează" fontSize: "Mărimea fontului" +mediaListWithOneImageAppearance: "Înălțimea listelor media cu o singură imagine" +limitTo: "Limitează până la {x}" noFollowRequests: "Nu ai nicio cerere de urmărire în așteptare" openImageInNewTab: "Deschide imaginile în taburi noi" dashboard: "Panou de control" @@ -491,9 +567,12 @@ objectStorageUseSSLDesc: "Oprește această opțiune dacă nu vei folosi HTTPS p objectStorageUseProxy: "Conectează-te prin Proxy" objectStorageUseProxyDesc: "Oprește această opțiune dacă vei nu folosi un Proxy pentru conexiunile API-ului" objectStorageSetPublicRead: "Setează \"public-read\" pentru încărcare" +s3ForcePathStyleDesc: "Dacă s3ForcePathStyle este activat, numele compartimentului trebuie inclus în calea adresei URL, spre deosebire de numele de gazdă(hostname) al adresei URL. Poate fi necesar să activezi această setare atunci când utilizezi servicii precum o instanță Minio găzduită de sine(self-hosted)." serverLogs: "Loguri server" deleteAll: "Șterge tot" showFixedPostForm: "Arată caseta de postare în vârful cronologie" +showFixedPostFormInChannel: "Afișează formularul de postare în partea de sus a cronologiei (Canale)" +withRepliesByDefaultForNewlyFollowed: "Include în mod prestabilit răspunsurile utilizatorilor nou urmăriți în cronologie" newNoteRecived: "Sunt note noi" sounds: "Sunete" sound: "Sunete" @@ -503,37 +582,51 @@ showInPage: "Arată în pagină" popout: "Scoate în afară" volume: "Volum" masterVolume: "Volumul principal" +notUseSound: "Oprește sunetul" +useSoundOnlyWhenActive: "Sunetele se aud numai dacă fereastra de Misskey este activă" details: "Detalii" +renoteDetails: "Detalii de re-notare" chooseEmoji: "Alege un emoji" unableToProcess: "Această operație nu poate fi completată" -recentUsed: "Folosit recent" +recentUsed: "Folosit(e) recent" install: "Instalează" uninstall: "Dezinstalează" installedApps: "Aplicații autorizate" nothing: "Nu e nimic de văzut aici" installedDate: "Autorizat la data de" -lastUsedDate: "Folosit ultima oara la" +lastUsedDate: "Folosit(e) ultima oara la" state: "Stare" sort: "Sortează" ascendingOrder: "Crescător" descendingOrder: "Descrescător" scratchpad: "Scratchpad" scratchpadDescription: "Scratchpad-ul oferă un mediu de experimentare în AiScript. Poți scrie, executa și verifica rezultatele acestuia interacționând cu Misskey în el." +uiInspector: "Inspector UI" +uiInspectorDescription: "Poți vedea lista de servere de componente UI în memorie. Componenta UI va fi generată de funcția Ui:C:." output: "Ieșire" script: "Script" disablePagesScript: "Dezactivează AiScript în Pagini" updateRemoteUser: "Actualizează informațiile utilizatorului extern" +unsetUserAvatar: "Anulează avatarul" +unsetUserAvatarConfirm: "Ești sigur(ă) că vrei sa anulezi avatarul?" +unsetUserBanner: "Avatarul utilizatorului a fost anulat" +unsetUserBannerConfirm: "Ești sigur(ă) că vrei sa anulezi bannerul?" deleteAllFiles: "Șterge toate fișierele" deleteAllFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele?" -removeAllFollowing: "Dezurmărește toți utilizatorii urmăriți" -removeAllFollowingDescription: "Asta va dez-urmări toate conturile din {host}. Te rog execută asta numai dacă instanța, de ex., nu mai există." +removeAllFollowing: "Elimină toți utilizatorii urmăriți" +removeAllFollowingDescription: "Asta va elimina urmărirea tuturor conturilor din {host}. Te rog execută asta numai dacă instanța, de ex., nu mai există." userSuspended: "Acest utilizator a fost suspendat." userSilenced: "Acest utilizator a fost setat silențios." yourAccountSuspendedTitle: "Acest cont a fost suspendat" yourAccountSuspendedDescription: "Acest cont a fost suspendat din cauza încălcării termenilor de serviciu al serverului sau ceva similar. Contactează administratorul dacă ai dori să afli un motiv mai detaliat. Te rog nu crea un cont nou." +tokenRevoked: "Token invalid" +tokenRevokedDescription: "Token-ul a expirat.\nTe rugăm sa te reloghezi." +accountDeleted: "Cont șters." +accountDeletedDescription: "Acest cont a fost eliminat." menu: "Meniu" divider: "Separator" addItem: "Adaugă element" +rearrange: "Rearanjează" relays: "Relee" addRelay: "Adaugă Releu" inboxUrl: "URL-ul inbox-ului" @@ -556,9 +649,11 @@ author: "Autor" leaveConfirm: "Ai schimbări nesalvate. Vrei să renunți la ele?" manage: "Gestionare" plugins: "Pluginuri" +preferencesBackups: "Copii de rezervă ale preferințelor" deck: "Deck" undeck: "Părăsește Deck" useBlurEffectForModal: "Folosește efect de blur pentru modale" +useFullReactionPicker: "Utilizează selectorul de reacții de dimensiune completă" width: "Lăţime" height: "Înălţime" large: "Mare" @@ -566,6 +661,7 @@ medium: "Mediu" small: "Mic" generateAccessToken: "Generează token de acces" permission: "Permisiuni" +adminPermission: "Permisiuni administrator" enableAll: "Actevează tot" disableAll: "Dezactivează tot" tokenRequested: "Acordă acces la cont" @@ -587,20 +683,26 @@ smtpSecure: "Folosește SSL/TLS implicit pentru conecțiunile SMTP" smtpSecureInfo: "Oprește opțiunea asta dacă STARTTLS este folosit" testEmail: "Testează livrarea emailurilor" wordMute: "Cuvinte pe mut" +wordMuteDescription: "Minimizează notele care conțin cuvântul sau expresia specificată. Notele minimizate pot fi afișate făcând clic pe ele." +hardWordMute: "Amuțire pe cuvinte grele" +showMutedWord: "Arata cuvintele amuțite" +hardWordMuteDescription: "Ascunde notele care conțin fraza specificată. Spre deosebire de cuvintele amuțite, notele vor fi complet ascunse." regexpError: "Eroare de Expresie Regulată" regexpErrorDescription: "A apărut o eroare în expresia regulată pe linia {line} al cuvintelor {tab} setate pe mut:" instanceMute: "Instanțe pe mut" userSaysSomething: "{name} a spus ceva" +userSaysSomethingAbout: "{name} a scris ceva despre {name}" makeActive: "Activează" display: "Arată" copy: "Copiază" +copiedToClipboard: "Copiat în clipboard." metrics: "Metrici" overview: "Privire de ansamblu" logs: "Log-uri" delayed: "Întârziate" database: "Baza de date" channel: "Canale" -create: "Crează" +create: "Creează" notificationSetting: "Setări notificări" notificationSettingDesc: "Selectează tipurile de notificări care să fie arătate" useGlobalSetting: "Folosește setările globale" @@ -608,12 +710,14 @@ useGlobalSettingDesc: "Dacă opțiunea e pornită, notificările contului tău v other: "Altele" regenerateLoginToken: "Regenerează token de login" regenerateLoginTokenDescription: "Regenerează token-ul folosit intern în timpul logări. În mod normal asta nu este necesar. Odată regenerat, toate dispozitivele vor fi delogate." +theKeywordWhenSearchingForCustomEmoji: "Acesta este cuvântul cheie atunci când cauți emoji-uri personalizate." setMultipleBySeparatingWithSpace: "Separă mai multe intrări cu spații." fileIdOrUrl: "Introdu ID sau URL" behavior: "Comportament" sample: "exemplu" abuseReports: "Rapoarte" reportAbuse: "Raportează" +reportAbuseRenote: "Raportați Re-nota" reportAbuseOf: "Raportează {name}" fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacă este despre o notă specifică, te rog introdu URL-ul ei." abuseReported: "Raportul tău a fost trimis. Mulțumim." @@ -625,46 +729,556 @@ openInNewTab: "Deschide în tab nou" openInSideView: "Deschide în vedere laterală" defaultNavigationBehaviour: "Comportament de navigare implicit" editTheseSettingsMayBreakAccount: "Editarea acestor setări îți pot defecta contul." +instanceTicker: "Informații de instanță ale notelor" waitingFor: "Așteptând pentru {x}" -random: "Aleator" +random: "Aleatoriu" system: "Sistem" switchUi: "Schimbă UI" desktop: "Desktop" +clip: "Clip" +createNew: "Creează ceva nou" +optional: "Opțional" +createNewClip: "Creează un clip nou" +unclip: "Anulează clipul" +confirmToUnclipAlreadyClippedNote: "Această notă face deja parte din clipul „{name}”. Dorești, în schimb, să îl elimini din acest clip?" +public: "Public" +private: "Privat" +i18nInfo: "Misskey este tradusă în diferite limbi de către voluntari. Puteți ajuta accesând {link}." +manageAccessTokens: "Gestionați token-urile de acces" +accountInfo: "Informațiile contului" +notesCount: "Numărul de note" +repliesCount: "Numărul de răspunsuri trimise" +renotesCount: "Numărul de Re-Note trimise" +repliedCount: "Numărul de răspunsuri primite" +renotedCount: "Numărul de Re-Note primite" +followingCount: "Numărul de conturi urmărite" +followersCount: "Numărul de urmăritori" +sentReactionsCount: "Numărul de reacții trimise" +receivedReactionsCount: "Numărul de reacții primite" +pollVotesCount: "Numărul de voturi trimise la sondaj" +pollVotedCount: "Numărul de voturi în sondaj" +yes: "Da" +no: "Nu" +driveFilesCount: "Numărul de fișiere din drive" +driveUsage: "Gestionati spatiul de utilizare a drive-ului" +noCrawle: "Respingeți indexarea prin crawler" +noCrawleDescription: "Cere motoarelor de căutare să nu indexeze pagina de profil, noteele, paginile etc." +lockedAccountInfo: "Dacă nu setați vizibilitatea notei la „Numai persoane interesate”, notele vor fi vizibile pentru oricine, chiar dacă aveți nevoie de aprobarea manuală a persoanelor interesate." +alwaysMarkSensitive: "Marcați ca sensibil în mod prestabilit" +loadRawImages: "Încărcați imagini originale în loc să afișați miniaturile" +disableShowingAnimatedImages: "Nu reda imaginile animate" +highlightSensitiveMedia: "Evidențiază conținutul media sensibil" +verificationEmailSent: "A fost trimis un e-mail de confirmare. Urmează linkul din e-mail pentru a finaliza configurarea." +notSet: "Nesetat" +emailVerified: "E-mailul a fost verificat" +noteFavoritesCount: "Numărul de note preferate" +pageLikesCount: "Numărul de pagini apreciate" +pageLikedCount: "Numărul de aprecieri primite pe pagină" +contact: "Contact" +useSystemFont: "Utilizați fontul implicit al sistemului" +clips: "Clip" +experimentalFeatures: "Funcții experimentale" +experimental: "Experimental" +thisIsExperimentalFeature: "Aceasta este o funcție experimentală. Funcționalitatea sa este supusă modificării și este posibil să nu funcționeze conform intenției." +developer: "Dezvoltator" +makeExplorable: "Fă-ți contul vizibil în secțiunea„Explorați”" +makeExplorableDescription: "Dacă dezactivezi această opțiune, contul dvs. nu va fi vizibil în secțiunea\"Explorați\"." +duplicate: "Duplicat" +left: "Stânga" +center: "Centru" +wide: "Lat" +narrow: "Îngust" +reloadToApplySetting: "Setările vor fi replicate după reîncărcarea paginii." +needReloadToApply: "Este necesară o reîncărcare pentru ca acest lucru să se replice." +showTitlebar: "Afișează bara de titlu" clearCache: "Golește cache-ul" +onlineUsersCount: "{n} de utilizatori online" +nUsers: "{n} Utilizatori" +nNotes: "{n} de note" +sendErrorReports: "Trimite rapoartele de eroare" +sendErrorReportsDescription: "Când este pornit, informațiile detaliate despre erori vor fi partajate cu Misskey atunci când apare o problemă, ajutând la îmbunătățirea calității Misskey.\nAceasta va include informații precum versiunea sistemului de operare, ce browser utilizați, activitatea dvs. în Misskey etc." +myTheme: "Tema mea" +backgroundColor: "Culoare de fundal" +accentColor: "Culoare de accent" +textColor: "Culoarea textului" +saveAs: "Salvează ca..." +advanced: "Avansat" +advancedSettings: "Setări Avansate" +value: "Valoare" +createdAt: "Creat în" +updatedAt: "Actualizat la" +saveConfirm: "Salvezi modificările?" +deleteConfirm: "Sigur vrei să ștergi?" +invalidValue: "Valoare invalidă." +registry: "Registru" +closeAccount: "Șterge contul" +currentVersion: "Versiunea curentă" +latestVersion: "Versiunea cea mai nouă" +youAreRunningUpToDateClient: "Utilizezi cea mai nouă versiune a clientului" +newVersionOfClientAvailable: "Este disponibilă o nouă versiune a clientului." +usageAmount: "Utilizare" +capacity: "Capacitate" +inUse: "Folosit" +editCode: "Editează codul" +apply: "Aplică" +receiveAnnouncementFromInstance: "Primește notificări de la această instanță" +emailNotification: "Notificări prin e-mail" +publish: "Publică" +inChannelSearch: "Caută pe canal" +useReactionPickerForContextMenu: "Deschide selectorul de reacții făcând clic dreapta" +typingUsers: "{users} scriu/e chiar acum..." +jumpToSpecifiedDate: "Sari la o anumită dată" +showingPastTimeline: "În prezent, se afișează o cronologie veche" +clear: "Întoarce-te" +markAllAsRead: "Marchează ca ,,citit”" +goBack: "Înapoi" +unlikeConfirm: "Chiar îți elimini like-ul?" +fullView: "Ecran complet" +quitFullView: "Ieși din ecranul complet" +addDescription: "Adaugă o descriere" +userPagePinTip: "Poți afișa notele aici selectând „fixează pe profil” din meniul individual al fiecărei note " +notSpecifiedMentionWarning: "Există mențiuni ce nu sunt incluse în lista de destinatari" info: "Despre" +userInfo: "Informații despre utilizator" +unknown: "Necunoscut" +onlineStatus: "Stare online" +hideOnlineStatus: "Ascunde starea online" +hideOnlineStatusDescription: "Ascunderea stării dvs. online reduce confortul unor funcții, cum ar fi căutarea." +online: "Online" +active: "Disponibil" +offline: "Offline" +notRecommended: "Nerecomandat" +botProtection: "Protecție boți" +instanceBlocking: "Instanțe blocate/ascunse" +selectAccount: "Selectează un cont" +switchAccount: "Schimbă contul" +enabled: "Activat" +disabled: "Dezactivat" +quickAction: "Acțiuni rapide" user: "Utilizatori" administration: "Gestionare" +accounts: "Conturi" +switch: "Schimbă" +noMaintainerInformationWarning: "Informațiile întreținătorului nu sunt configurate." +noInquiryUrlWarning: "Adresa URL de cereri de informații nu este setata" +noBotProtectionWarning: "Protecția împotriva boților nu este configurată." +configure: "Configurează" +postToGallery: "Creează o postare nouă în galerie" +postToHashtag: "Postează pe acest hashtag" +gallery: "Galerie" +recentPosts: "Postări recente" +popularPosts: "Postări populare" +shareWithNote: "Distribuie cu notă" +ads: "Reclame" +expiration: "Termen limită" +startingperiod: "Start" +memo: "Memo" +priority: "Prioritate" +high: "Ridicată" middle: "Mediu" +low: "Scăzuta" +emailNotConfiguredWarning: "Adresa de e-mail nu este setată." +ratio: "Rație" +previewNoteText: "Afișează previzualizarea" +customCss: "CSS personalizat" +customCssWarn: "Această setare ar trebui folosită numai dacă știi ce face. Introducerea unor valori necorespunzătoare poate determina clientul să nu mai funcționeze normal." +global: "Global" +squareAvatars: "Afișează avatarele pătrate" sent: "Trimite" +received: "Primite" +searchResult: "Rezultate căutare" +hashtags: "Hashtag-uri" +troubleshooting: "Diagnosticare" +useBlurEffect: "Utilizează efecte de estompare în interfața de utilizare" +learnMore: "Află mai multe" +misskeyUpdated: "Misskey a fost actualizat!" +whatIsNew: "Vezi noile modificări" +translate: "Tradu" +translatedFrom: "Tradus din {x}" +accountDeletionInProgress: "Ștergerea contului este în curs de desfășurare" +usernameInfo: "Un nume care vă identifică contul de alții de pe acest server. Poți folosi alfabetul (a~z, A~Z), cifrele (0~9) sau litere de subliniere (_). Numele de utilizator nu pot fi schimbate ulterior." +aiChanMode: "Modul Ai" +devMode: "Modul Dezvoltator" +keepCw: "Păstrează avertismentele de conținut" +pubSub: "Conturi de Pub/Sub" +lastCommunication: "Ultima comunicare" +resolved: "Rezolvat" +unresolved: "Nerezolvat" +breakFollow: "Elimină urmăritorul" +breakFollowConfirm: "Chiar eliminați această urmărire?" +itsOn: "Activat" +itsOff: "Dezactivat" +on: "Pornit" +off: "Oprit" +emailRequiredForSignup: "E nevoie de o adresă de e-mail pentru înregistrare" +unread: "Necitit/e" +filter: "Filtru" +controlPanel: "Panou de Control" +manageAccounts: "Gestionează Conturile" +makeReactionsPublic: "Setați istoricul reacțiilor să fie public" +makeReactionsPublicDescription: "Faceți-vă reacțiile vizibile pentru toată lumea" +classic: "Clasic" +muteThread: "Amuțește thread-ul" +unmuteThread: "Dezmuțește thread-ul" +followingVisibility: "Vizibilitatea celor pe care ii urmărești" +followersVisibility: "Vizibilitatea celor care te urmărește" +continueThread: "Continuă thread-ul" +deleteAccountConfirm: "Acest lucru vă va șterge ireversibil contul. Continui?" +incorrectPassword: "Parolă incorectă." +incorrectTotp: "Parola unică este incorectă sau a expirat." +voteConfirm: "Confirmi votul pentru „{choice}”?" +hide: "Ascunde" +useDrawerReactionPickerForMobile: "Afișează selectorul de reacții ca sertar pe mobil" +welcomeBackWithName: "Bine ai revenit, {name}" +clickToFinishEmailVerification: "Dați clic pe [{ok}] pentru a finaliza verificarea e-mailului." +overridedDeviceKind: "Tipul de dispozitiv" +smartphone: "Smartphone" +tablet: "Tableta" +auto: "Auto" +themeColor: "Culoarea temei" +size: "Dimensiune" +numberOfColumn: "Numărul de coloane" searchByGoogle: "Caută" +instanceDefaultLightTheme: "Tema luminoasă implicită la nivelul întregii instanțe" +instanceDefaultDarkTheme: "Tema întunecată implicită la nivelul întregii instanțe" +instanceDefaultThemeDescription: "Introduceți codul temei în format obiect." +mutePeriod: "Durata amuțire" +period: "Timp limită" +indefinitely: "Permanent" +tenMinutes: "10 minute" +oneHour: "O oră" +oneDay: "O zi" +oneWeek: "O săptămâna" +oneMonth: "O lună" +threeMonths: "Trei luni" +oneYear: "Un an" +threeDays: "Trei zile" +reflectMayTakeTime: "Poate dura ceva timp pentru ca acest lucru să se replice." +failedToFetchAccountInformation: "Nu s-a putut prelua informațiile despre cont" +rateLimitExceeded: "Limita ratei a fost depășită" +cropImage: "Trunchiază imaginea" +cropImageAsk: "Dorești să trunchiezi această imagine?" +cropYes: "Trunchiază" +cropNo: "Utilizează-o așa cum e" file: "Fișiere" +recentNHours: "Ultimele {n} ore" +recentNDays: "Ultimele {n} zile" +noEmailServerWarning: "Serverul de e-mail nu este configurat." +thereIsUnresolvedAbuseReportWarning: "Sunt rapoarte nerezolvate." +recommended: "Recomandat" +check: "Verifică" +driveCapOverrideLabel: "Schimbă capacitatea de stocare a drive-ului pentru acest utilizator" +driveCapOverrideCaption: "Resetează capacitatea la valoarea implicită introducând o valoare de 0 sau mai mică." +requireAdminForView: "Trebuie să te conectezi cu un cont de administrator pentru a vedea această resursă." +isSystemAccount: "Un cont creat și operat automat de sistem." +typeToConfirm: "Introdu {x} pentru a confirma" +deleteAccount: "Șterge contul" +document: "Documentație" +numberOfPageCache: "Număr de pagini stocate cache" +numberOfPageCacheDescription: "Mărirea acestui număr va îmbunătăți conveniența, dar va cauza mai multă sarcină pe măsură ce se utilizează mai multă memorie pe dispozitivul utilizatorului.\n" +logoutConfirm: "Ești sigur(ă) că vrei să te deloghezi?" +logoutWillClearClientData: "Deconectarea va șterge setările clientului din browser. Pentru a putea restabili setările la autentificare, trebuie să activezi copia de rezervă automată a setărilor." +lastActiveDate: "Ultima dată de utilizare" +statusbar: "Bară de stare" +pleaseSelect: "Alege o opțiune" +reverse: "Invers" +colored: "Colorat" +refreshInterval: "Interval de actualizare" +label: "Etichetă" +type: "Tip" +speed: "Viteză" +slow: "Lent" +fast: "Rapid" +sensitiveMediaDetection: "Detectarea conținutului media sensibil" +localOnly: "Beta" +remoteOnly: "Doar externe" +failedToUpload: "Încărcare eșuată" +cannotUploadBecauseInappropriate: "Acest fișier nu a putut fi încărcat deoarece părți din acesta au fost detectate ca potențial neadecvate." +cannotUploadBecauseNoFreeSpace: "Încărcarea a eșuat datorită lipsei spațiului din drive." +cannotUploadBecauseExceedsFileSizeLimit: "Acest fișier nu poate fi încărcat deoarece depășește limita de dimensiune a fișierelor." +beta: "Beta" +enableAutoSensitive: "Marcare automată ca fiind conținut sensibil" +enableAutoSensitiveDescription: "Permite detectarea și marcarea automată a mediilor sensibile prin Machine Learning acolo unde este posibil. Chiar dacă această opțiune este dezactivată ea poate fi, în schimb, activă la nivelul întregii instanțe." +activeEmailValidationDescription: "Permite validarea mai strictă a adreselor de e-mail, care includ verificarea adreselor de unică folosință și dacă pot fi comunicate cu acestea. Când este debifat, este validat doar formatul e-mailului." +navbar: "Bara de navigare" +shuffle: "Amestecă" +account: "Conturi" +move: "Mută" +pushNotification: "Notificări tip „push”" +subscribePushNotification: "Permite notificările tip „push”" +unsubscribePushNotification: "Oprește notificările tip „push”" +pushNotificationAlreadySubscribed: "Notificările tip „push” sunt deja activate" +pushNotificationNotSupported: "Browserul sau instanța dvs. nu acceptă notificările tip „push”" +sendPushNotificationReadMessage: "Șterge notificările tip „push” după ce au fost citite" +sendPushNotificationReadMessageCaption: "Acest lucru poate crește consumul de energie al dispozitivului" +windowMaximize: "Maximizează" +windowMinimize: "Minimizează" +windowRestore: "Restabilește" +caption: "Titrare" +loggedInAsBot: "Conectat în prezent ca bot" +tools: "Unelte" +cannotLoad: "Nu se poate încărca" +numberOfProfileView: "Numărul de vizualizări ale profilului" +like: "Îmi place!" +unlike: "Îmi displace" +numberOfLikes: "Numărul de aprecieri" show: "Arată" +neverShow: "Nu mai afișa" +remindMeLater: "Poate mai târziu" +didYouLikeMisskey: "A început sa îți placa Misskey?" +pleaseDonate: "{host} folosește software-ul gratuit, Misskey. Am aprecia foarte mult donațiile dumneavoastră, astfel încât dezvoltarea Misskey să poată continua!" +correspondingSourceIsAvailable: "Codul sursă corespunzător este disponibil la {anchor}" +roles: "Roluri" +role: "Roluri" +noRole: "Rolul nu a fost găsit" +normalUser: "Utilizator obișnuit" +undefined: "Nedefinit" +assign: "Asignează" +unassign: "Dezasignează" +color: "Culoare" +manageCustomEmojis: "Gestionează emoji-uri personalizate" +manageAvatarDecorations: "Gestionați decorațiunile avatarului" +youCannotCreateAnymore: "Ai atins limita de creație." +cannotPerformTemporary: "Temporar indisponibil" +cannotPerformTemporaryDescription: "Această acțiune nu poate fi efectuată temporar din cauza depășirii limitei de execuție. Te rugăm să aștepți puțin și apoi să încerci din nou." +invalidParamError: "Parametri invalizi" +invalidParamErrorDescription: "Parametrii cererii sunt invalizi. Acest lucru este cauzat în mod normal de o eroare, dar se poate datora și intrărilor care depășesc limitele de dimensiune sau altceva similar." +permissionDeniedError: "Operațiune refuzată" +permissionDeniedErrorDescription: "Acest cont nu are permisiunea de a efectua această acțiune." +preset: "Presetate" +selectFromPresets: "Alege din presetate" +achievements: "Realizări" +gotInvalidResponseError: "Răspunsul serverului este invalid" +gotInvalidResponseErrorDescription: "Serverul poate fi oprit sau e în curs de întreținere. Te rugăm să încerci din nou după un timp." +thisPostMayBeAnnoying: "Această notă îi poate deranja pe alții." +thisPostMayBeAnnoyingHome: "Postează în cronologia de acasă" +thisPostMayBeAnnoyingCancel: "Anulează" +thisPostMayBeAnnoyingIgnore: "Postează oricum" +collapseRenotes: "Restrânge Re-Notările pe care le-ați văzut deja" +collapseRenotesDescription: "Restrânge notările pe care le-ați văzut deja" +internalServerError: "Eroare interna a serverului" +internalServerErrorDescription: "Serverul a întâmpinat o eroare neașteptată." +copyErrorInfo: "Copiază detaliile erorii" +joinThisServer: "Înregistrează-te în această instanță" +exploreOtherServers: "Caută o altă instanță" +letsLookAtTimeline: "Aruncă o privire la cronologie" +disableFederationConfirm: "Sigur vrei sa oprești federarea" +disableFederationConfirmWarn: "Chiar dacă sunt defederate, postările vor continua să fie publice, dacă nu sunt stabilite altfel. De obicei, nu trebuie să faceți acest lucru." +disableFederationOk: "Dezactivează" +invitationRequiredToRegister: "Acest server este în prezent accesibil numai pe bază de invitație. Se pot înregistra doar cei care au cod de invitație." +emailNotSupported: "Această instanță nu acceptă trimiterea de e-mailuri" +postToTheChannel: "Postează pe canal" +cannotBeChangedLater: "Nu poate fi schimbat ulterior" +reactionAcceptance: "Acceptarea reacțiilor" +likeOnly: "Doar aprecieri" +likeOnlyForRemote: "Toate (aplicabil numai pentru instanțe externe)" +nonSensitiveOnly: "Numai conținut non-sensibil" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Numai non-sensibile (aplicabil numai pentru aprecieri de la surse externe)" +rolesAssignedToMe: "Roluri asignate mie" +resetPasswordConfirm: "Sigur vrei sa îți resetezi parola" +sensitiveWords: "Cuvinte sensibile" +sensitiveWordsDescription: "Vizibilitatea tuturor notelor care conțin oricare dintre cuvintele configurate va fi setate automat la „Acasă”. Puteți enumera mai multe, separându-le prin o linie de spațiere nouă." +sensitiveWordsDescription2: "Folosirea spațiilor va crea expresii \"AND\" și înconjurând cuvintele cheie cu bare oblice le vor transforma într-o expresie obișnuită." +prohibitedWords: "Cuvinte interzise" +prohibitedWordsDescription: "Activează o eroare la încercarea de a posta o notă care conține cuvintele setate. Pot fi setate mai multe cuvinte, separate printr-o linie de spațiere nouă." +prohibitedWordsDescription2: "Folosirea spațiilor va crea expresii \"AND\" și înconjurând cuvintele cheie cu bare oblice le vor transforma într-o expresie obișnuită." +hiddenTags: "Hashtag-uri ascunse" +hiddenTagsDescription: "Selectați hashtag-uri care nu vor fi afișate în lista de tendințe.\nMai multe hashtag-uri pot fi înregistrate pe o linie de spațiere noua." +notesSearchNotAvailable: "Căutarea notelor este indisponibilă." +license: "Licență" +unfavoriteConfirm: "Sigur vrei să elimini din favorite?" +myClips: "Clipurile mele" +drivecleaner: "Curățitorul de drive" +retryAllQueuesNow: "Reîncearcă să rulezi toate cozile" +retryAllQueuesConfirmTitle: "Sigur vrei să le reîncerci din nou?" +retryAllQueuesConfirmText: "Acest lucru va crește temporar încărcarea rulării serverului." +enableChartsForRemoteUser: "Generează diagrame cu datele utilizatorilor externi" +enableChartsForFederatedInstances: "Generează diagrame de date ale instanțelor externe" +enableStatsForFederatedInstances: "Primește statistici ale serverelor externe" +showClipButtonInNoteFooter: "Adaugă „Clip” la meniul de acțiuni pentru note" +reactionsDisplaySize: "Dimensiunea afișajului de reacție" +limitWidthOfReaction: "Limitează lățimea maximă a reacțiilor și afișează-le în dimensiuni reduse." +noteIdOrUrl: "ID sau URL-ul notei" +video: "Video" +videos: "Video-uri" +audio: "Audio" +audioFiles: "Audio" +dataSaver: "Economizor de date" +accountMigration: "Migrarea contului" +accountMoved: "Acest utilizator a fost mutat într-un alt cont:" +accountMovedShort: "Acest cont a fost migrat." +operationForbidden: "Operațiune interzisă" +forceShowAds: "Afișează întotdeauna reclame" +addMemo: "Adaugă un memo" +editMemo: "Editează memo-ul" +reactionsList: "Reacții" +renotesList: "Re-Notări" +notificationDisplay: "Notificări" +leftTop: "Stânga-sus" +rightTop: "Dreapta-sus" +leftBottom: "Stânga-jos" +rightBottom: "Dreapta-jos" +stackAxis: "Direcția de stack-are" +vertical: "Vertical" +horizontal: "Orizontal" +position: "Poziție" +serverRules: "Regulamentul serverului" +pleaseConfirmBelowBeforeSignup: "Pentru a te înregistra pe acest server, trebuie să examinezi și să fii de acord cu următoarele:" +pleaseAgreeAllToContinue: "Trebuie să fii de acord cu toate câmpurile de mai sus pentru a continua." +continue: "Continuă" +preservedUsernames: "Nume rezervate de utilizator" +preservedUsernamesDescription: "Listeaza numele de utilizatori pentru a le rezerva, separate prin întreruperi de linie. Acestea vor deveni inutilizabile în timpul creării normale a contului, dar pot fi folosite de administratori pentru a crea conturi manual. Conturile deja existente care folosesc aceste nume de utilizator nu vor fi afectate." +createNoteFromTheFile: "Compuneți o notă din acest fișier" +archive: "Arhivă" +archived: "Arhivat" +unarchive: "Nearhivabil" +channelArchiveConfirmTitle: "Sigur vrei să arhivezi {name}?" +channelArchiveConfirmDescription: "Un canal arhivat nu va mai apărea în lista de canale sau în rezultatele căutării. De asemenea, postările noi nu mai pot fi adăugate la acesta." +thisChannelArchived: "Acest canal a fost arhivat." +displayOfNote: "Afișajul notelor" +initialAccountSetting: "Configurarea Profilului" +youFollowing: "Îl urmărești" +preventAiLearning: "Respinge utilizarea în Machine Learning (IA generativă)" +preventAiLearningDescription: "Solicită crawlerilor să nu folosească textul sau materialul de imagine postat etc. în seturile de date de învățare automată (AI predictivă/generativă). Acest lucru se realizează prin adăugarea unui flag „noai” HTML-Response la conținutul respectiv. Cu toate acestea, o prevenire completă nu poate fi realizată prin acest flag, deoarece poate fi pur și simplu ignorat." +options: "Opțiuni" +specifyUser: "Utilizator specific" +lookupConfirm: "Vrei să cauți?" +openTagPageConfirm: "Vrei să deschizi o pagină cu hashtag?" +specifyHost: "O gazdă(host) specifică" +failedToPreviewUrl: "Nu se poate previzualiza" +update: "Actualizare" +rolesThatCanBeUsedThisEmojiAsReaction: "Roluri care pot folosi acest emoji ca reacție" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Dacă nu sunt specificate rolurile, cineva poate folosi acest emoji ca reacție." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Aceste roluri trebuie să fie publice." +cancelReactionConfirm: "Ești sigur(ă) că vrei să ștergi reacția ta?" +changeReactionConfirm: "Sigur vrei sa îți ștergi reacția?" +later: "Mai târziu" +goToMisskey: "Spre Misskey" +additionalEmojiDictionary: "Dicționare emoji suplimentare" +installed: "Instalat" +branding: "Branding" +enableServerMachineStats: "Publicați statistici hardware ale serverului" +enableIdenticonGeneration: "Activați generarea identicon a utilizatorului" +turnOffToImprovePerformance: "Oprirea acestei opțiuni poate crește performanța." +createInviteCode: "Generează invitația" +createWithOptions: "Generează cu opțiuni" +createCount: "Numărul de invitații" +inviteCodeCreated: "Invitație generată" +inviteLimitExceeded: "Ați depășit limita invitațiilor pe care le puteți genera." +createLimitRemaining: "Limită invitații : {limit} rămase" +inviteLimitResetCycle: "Această limită se va reseta la {limit} la {time}." +expirationDate: "Data de expirare" +noExpirationDate: "Fără expirare" +inviteCodeUsedAt: "Codul de invitație în" +registeredUserUsingInviteCode: "Invitație folosita de" +waitingForMailAuth: "Verificarea e-mailului este în așteptare" +inviteCodeCreator: "Invitație creată de" +usedAt: "Folosit în" +unused: "Neutilizat" +used: "Utilizat" +expired: "Expirat" +doYouAgree: "De-acord?" +beSureToReadThisAsItIsImportant: "Te rugăm citește informația aceasta importantă" +iHaveReadXCarefullyAndAgree: "Am citit textul „{x}” și sunt de acord." +dialog: "Dialog" icon: "Avatar" +forYou: "Pentru tine" +currentAnnouncements: "Anunțuri curente" +pastAnnouncements: "Anunțuri anterioare" +youHaveUnreadAnnouncements: "Sunt anunțuri necitite." +useSecurityKey: "Te rugăm să urmezi instrucțiunile browserului sau ale dispozitivului tău pentru a-ți folosi cheia de securitate sau de acces." replies: "Răspunde" -renotes: "Re-notează" +renotes: "Re-Note" +loadReplies: "Afișează răspunsurile" +loadConversation: "Afișează conversația" +pinnedList: "Lista fixată" +keepScreenOn: "Menține ecranul aprins" +verifiedLink: "Deținerea linkului a fost verificată" +notifyNotes: "Notifică-mă despre notele noi" +unnotifyNotes: "Nu mai mă notifica despre notele noi" +authentication: "Autentificare" +authenticationRequiredToContinue: "Te rugăm să te autentifici pentru a continua" +dateAndTime: "Data și ora" +showRenotes: "Afiseaza Re-Notele" +edited: "Editat" +notificationRecieveConfig: "Setări de notificare" +mutualFollow: "Vă urmăriți" +followingOrFollower: "Urmărit sau urmăritor" +fileAttachedOnly: "Numai Note cu fișiere" +showRepliesToOthersInTimeline: "Afișează răspunsurile către ceilalți în cronologie" +hideRepliesToOthersInTimeline: "Ascunde răspunsurile către ceilalți în cronologie" +showRepliesToOthersInTimelineAll: "Afișează răspunsurile către ceilalți de către cei ce ii urmărești în cronologie" +repositoryUrlDescription: "Dacă utilizați Misskey așa cum este (fără modificări ale codului sursă), introduceți https://github.com/misskey-dev/misskey" +flip: "Invers" +copyReplayData: "Copiază datele de reluare" +lastNDays: "Ultimele {n} zile" +surrender: "Anulează" +copyPreferenceId: "Copiază ID-ul preferințelor" information: "Despre" +presets: "Presetate" +_imageEditing: + _vars: + filename: "Nume fișier" _chat: invitations: "Invită" noHistory: "Nu există istoric" members: "Membri" home: "Acasă" send: "Trimite" +_accountSettings: + requireSigninToViewContentsDescription2: "Conținutul nu va fi afișat în previzualizările URL (OGP), încorporate în paginile web sau pe serverele care nu acceptă citările de note." + makeNotesFollowersOnlyBefore: "Face ca notele anterioare pentru a fi afișate numai pentru urmăritori" _delivery: stop: "Suspendat" _type: none: "Publicare" +_initialTutorial: + _note: + reply: "Face clic pe acest buton pentru a răspunde la un mesaj. De asemenea, este posibil să răspunzi la răspunsuri, continuând conversația ca pe un șir de replici(thread)." + menu: "Poți vedea detaliile ce țin de Note, să copiezi linkuri și să efectuezi alte acțiuni." + _timeline: + social: "Vor fi afișate notele din cronologia „Acasă'' și „Locală''." + _postNote: + _visibility: + localOnly: "Postarea cu acest flag nu va federa nota pe alte servere. Utilizatorii de pe alte servere nu vor putea vizualiza aceste note direct, indiferent de setările de afișare de mai sus." + _cw: + description: "În locul corpului, va fi afișat conținutul scris în câmpul „comentarii”. Apăsând „citește mai mult” va dezvălui corpul." + useCases: "Acesta este folosit atunci când respectați instrucțiunile serverului, pentru notele necesare sau pentru auto-restrângerea spoilerului sau a textului sensibil." +_timelineDescription: + social: "Cronologia socială afișează note atât din cronologia de ,,Acasă'', cât și din cea ,,Locală\"." _role: + assignTarget: "Asignează" + priority: "Prioritate" _priority: + low: "Scăzuta" middle: "Mediu" + high: "Ridicată" + _options: + canManageCustomEmojis: "Gestionează emoji-uri personalizate" + canManageAvatarDecorations: "Gestionați decorațiunile avatarului" +_ffVisibility: + public: "Publică" +_ad: + back: "Înapoi" +_gallery: + my: "Galeria mea" + liked: "Postări apreciate" + like: "Îmi place!" + unlike: "Îmi displace" _email: _follow: - title: "te-a urmărit" + title: "Ai un nou urmăritor" +_instanceMute: + instanceMuteDescription: "Aceasta va dezactiva orice notă/renotă din instanțele enumerate, inclusiv cele ale utilizatorilor care răspund unui utilizator dintr-o instanță mută." _theme: description: "Descriere" keys: + fg: "Text" mention: "Mențiune" - renote: "Re-notează" + renote: "Re-Notează" divider: "Separator" + toastFg: "Textul din notificare" + fgHighlighted: "Textul evidențiat" _sfx: note: "Note" notification: "Notificări" @@ -672,6 +1286,11 @@ _ago: invalid: "Nu e nimic de văzut aici" _2fa: renewTOTPCancel: "Nu, mulțumesc." +_permissions: + "read:gallery": "Vizualizează-ți galeria" + "write:gallery": "Editează-ți galeria" + "read:gallery-likes": "Vizualizează-ți lista de postări apreciate din galerie" + "write:gallery-likes": "Editează-ți lista de postări apreciate din galerie" _widgets: profile: "Profil" instanceInfo: "Informații despre instanță" @@ -682,15 +1301,37 @@ _widgets: jobQueue: "coada de job-uri" _userList: chooseList: "Selectează o listă" +_widgetOptions: + height: "Înălţime" + _button: + colored: "Colorat" + _clock: + size: "Dimensiune" _cw: show: "Incarcă mai mult" _visibility: home: "Acasă" followers: "Urmăritori" + specified: "Note directe" +_postForm: + replyPlaceholder: "Răspunde la această notă..." + quotePlaceholder: "Citează aceasta nota..." + channelPlaceholder: "Postează pe un canal..." + _howToUse: + visibility_title: "Vizibilitate" + menu_title: "Meniu" + _placeholders: + a: "Ce mai faci?" + b: "Ce se mai petrece in jurul tău?" + c: "La ce te gândești?" + d: "Ce vrei să scrii?" + e: "Începe să scrii..." + f: "Te aștept să scrii..." _profile: name: "Nume" username: "Nume de utilizator" _exportOrImport: + clips: "Clip" followingList: "Urmărești" muteList: "Amuțește" blockingList: "Blochează" @@ -699,24 +1340,28 @@ _charts: federation: "Federație" _timelines: home: "Acasă" + local: "Local" + social: "Social" + global: "Global" _play: script: "Script" summary: "Descriere" _pages: blocks: + text: "Text" image: "Imagini" _notification: youWereFollowed: "te-a urmărit" _types: follow: "Urmărești" mention: "Mențiune" - renote: "Re-notează" + renote: "Re-Note" quote: "Citează" reaction: "Reacție" login: "Autentifică-te" _actions: reply: "Răspunde" - renote: "Re-notează" + renote: "Re-Notează" _deck: _columns: notifications: "Notificări" @@ -725,8 +1370,11 @@ _deck: list: "Liste" channel: "Canale" mentions: "Mențiuni" + direct: "Note directe" + roleTimeline: "Cronologia rolului" _webhookSettings: name: "Nume" + active: "Activat" _abuseReport: _notificationRecipient: _recipientType: @@ -734,10 +1382,42 @@ _abuseReport: _moderationLogTypes: suspend: "Suspendă" resetPassword: "Resetează parola" + createInvitation: "Generează invitația" + deleteGalleryPost: "Postarea din galerie a fost ștearsă" +_dataSaver: + _code: + title: "Evidențierea codului" + description: "Dacă notațiile de evidențiere a codului sunt utilizate în MFM etc., acestea nu se vor încărca până când sunt atinse. Evidențierea de sintaxă necesită descărcarea fișierelor de definiție de evidențiere pentru fiecare limbaj de programare. Prin urmare, dezactivarea încărcării automate a acestor fișiere este de așteptat să reducă cantitatea de date de comunicare." _reversi: total: "Total" +_contextMenu: + app: "Aplicație" + appWithShift: "Aplicatie ce utilizeaza tasta ,,shift\"" + native: "Nativ" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copiază rândurile selectate" + copySelectionRanges: "Copiază selecția" _remoteLookupErrors: _noSuchObject: title: "Nu a fost găsit" _search: searchScopeAll: "Tot" + searchScopeLocal: "Local" + searchScopeUser: "Utilizator specific" + serverHostPlaceholder: "Exemplu: misskey.example.com" +_watermarkEditor: + scale: "Dimensiune" + text: "Text" + position: "Poziție" + type: "Tip" + image: "Imagini" + advanced: "Avansat" +_imageEffector: + _fxProps: + scale: "Dimensiune" + size: "Dimensiune" + offset: "Poziție" +_qr: + showTabTitle: "Arată" + raw: "Text" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index e81af534e7..3baf31c4d0 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2,9 +2,10 @@ _lang_: "Русский" headlineMisskey: "Сеть, сплетённая из заметок" introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀" -poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом Misskey, называемый экземпляром Misskey." +poweredByMisskeyDescription: "{name} – один из инстансов (также называемый экземпляром Misskey), использующий платформу с открытым исходным кодом Misskey." monthAndDay: "{day}.{month}" search: "Поиск" +reset: "Сброс" notifications: "Уведомления" username: "Имя пользователя" password: "Пароль" @@ -48,6 +49,7 @@ pin: "Закрепить в профиле" unpin: "Открепить от профиля" copyContent: "Скопировать содержимое" copyLink: "Скопировать ссылку" +copyRemoteLink: "Скопировать ссылку на репост" copyLinkRenote: "Скопировать ссылку на репост" delete: "Удалить" deleteAndEdit: "Удалить и отредактировать" @@ -80,12 +82,14 @@ export: "Экспорт" files: "Файлы" download: "Скачать" driveFileDeleteConfirm: "Удалить файл «{name}»? Заметки с ним также будут удалены." -unfollowConfirm: "Удалить из подписок пользователя {name}?" +unfollowConfirm: "Отписаться от {name} ?" +cancelFollowRequestConfirm: "Вы уверены, что хотите отменить запрос на подписку пользователю {name}?" +rejectFollowRequestConfirm: "Отклонить запрос на подписку от {name}?" exportRequested: "Вы запросили экспорт. Это может занять некоторое время. Результат будет добавлен на «Диск»." importRequested: "Вы запросили импорт. Это может занять некоторое время." lists: "Списки" noLists: "Нет ни одного списка" -note: "Заметка" +note: "Пост" notes: "Заметки" following: "Подписки" followers: "Подписчики" @@ -120,7 +124,7 @@ inChannelRenote: "В канале" inChannelQuote: "Заметки в канале" renoteToChannel: "Репостнуть в канал" renoteToOtherChannel: "Репостнуть в другой канал" -pinnedNote: "Закреплённая заметка" +pinnedNote: "Закреплённый пост" pinned: "Закрепить в профиле" you: "Вы" clickToShow: "Нажмите для просмотра" @@ -197,7 +201,7 @@ searchWith: "Найденное «{q}»" youHaveNoLists: "У вас нет ни одного списка" followConfirm: "Подписаться на {name}?" proxyAccount: "Учётная запись прокси" -proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси." +proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например: если пользователь добавит кого-то с другого сайта в список, то деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси." host: "Хост" selectSelf: "Выбрать себя" selectUser: "Выберите пользователя" @@ -215,8 +219,10 @@ perDay: "По дням" stopActivityDelivery: "Остановить отправку обновлений активности" blockThisInstance: "Блокировать этот инстанс" silenceThisInstance: "Заглушить этот инстанс" +mediaSilenceThisInstance: "Заглушить сервер" operations: "Операции" software: "Программы" +softwareName: "Software Name" version: "Версия" metadata: "Метаданные" withNFiles: "Файлы, {n} шт." @@ -235,7 +241,11 @@ clearCachedFilesConfirm: "Удалить все закэшированные ф blockedInstances: "Заблокированные инстансы" blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом." silencedInstances: "Заглушённые инстансы" +silencedInstancesDescription: "Перечислите имена серверов, которые вы хотите отключить, разделив их новой строкой. Все учетные записи, принадлежащие к указанным в списке серверам, будут заблокированы и смогут отправлять запросы только на повторное использование и не смогут указывать локальные учетные записи, если они не будут отслеживаться. Это не повлияет на заблокированные серверы." +mediaSilencedInstances: "Заглушённые сервера" +mediaSilencedInstancesDescription: "Укажите названия серверов, для которых вы хотите отключить доступ к файлам, по одному серверу в строке. Все учетные записи, принадлежащие к перечисленным серверам, будут считаться конфиденциальными и не смогут использовать пользовательские эмодзи. Это никак не повлияет на заблокированные серверы." federationAllowedHosts: "Серверы, поддерживающие федерацию" +federationAllowedHostsDescription: "Укажите имена серверов, для которых вы хотите разрешить объединение, разделив их разделителями строк." muteAndBlock: "Скрытие и блокировка" mutedUsers: "Скрытые пользователи" blockedUsers: "Заблокированные пользователи" @@ -243,9 +253,9 @@ noUsers: "Нет ни одного пользователя" editProfile: "Редактировать профиль" noteDeleteConfirm: "Вы хотите удалить эту заметку?" pinLimitExceeded: "Нельзя закрепить ещё больше заметок" -intro: "Установка Misskey завершена! А теперь создайте учетную запись администратора." done: "Готово" processing: "Обработка" +preprocessing: "Подготовка..." preview: "Предпросмотр" default: "По умолчанию" defaultValueIs: "По умолчанию: {value}" @@ -291,9 +301,12 @@ uploadFromUrl: "Загрузить по ссылке" uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить" uploadFromUrlRequested: "Загрузка выбранного" uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время." +uploadNFiles: "Загрузить {n} файл" explore: "Обзор" messageRead: "Прочитали" +readAllChatMessages: "Отметить прочитанным" noMoreHistory: "История закончилась" +startChat: "Начать чат" nUsersRead: "Прочитали {n}" agreeTo: "Я соглашаюсь с {0}" agree: "Согласен" @@ -318,11 +331,13 @@ dark: "Тёмный" lightThemes: "Светлые темы" darkThemes: "Тёмные темы" syncDeviceDarkMode: "Синхронизировать с тёмной темой системы" +switchDarkModeManuallyWhenSyncEnabledConfirm: "Включена функция \"{x}\". Отключить синхронизацию, чтобы переключать режим вручную?" drive: "Диск" fileName: "Имя файла" selectFile: "Выберите файл" selectFiles: "Выберите файлы" selectFolder: "Выберите папку" +unselectFolder: "Снять выбор" selectFolders: "Выберите папки" fileNotSelected: "Файл не выбран" renameFile: "Переименовать файл" @@ -335,6 +350,7 @@ addFile: "Добавить файл" showFile: "Посмотреть файл" emptyDrive: "Диск пуст" emptyFolder: "Папка пуста" +dropHereToUpload: "Переместите файл сюда" unableToDelete: "Удаление невозможно" inputNewFileName: "Введите имя нового файла" inputNewDescription: "Введите новую подпись" @@ -387,7 +403,7 @@ pinnedUsersDescription: "Перечислите по одному имени п pinnedPages: "Закрепленные страницы" pinnedPagesDescription: "Если хотите закрепить страницы на главной сайта, сюда можно добавить пути к ним, каждый в отдельной строке." pinnedClipId: "Идентификатор закреплённой подборки" -pinnedNotes: "Закреплённая заметка" +pinnedNotes: "Закреплённый пост" hcaptcha: "hCaptcha" enableHcaptcha: "Включить hCaptcha" hcaptchaSiteKey: "Ключ сайта" @@ -416,6 +432,7 @@ antennaExcludeBots: "Исключать ботов" antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них." notifyAntenna: "Уведомлять о новых заметках" withFileAntenna: "Только заметки с вложениями" +excludeNotesInSensitiveChannel: "Исключить заметки из конфиденциальных каналов" enableServiceworker: "Включить ServiceWorker" antennaUsersDescription: "Пишите каждое название аккаута на отдельной строке" caseSensitive: "С учётом регистра" @@ -446,6 +463,8 @@ totpDescription: "Описание приложения-аутентификат moderator: "Модератор" moderation: "Модерация" moderationNote: "Примечания модератора" +moderationNoteDescription: "Вы можете заполнять заметки, которые будут доступны только модераторам." +addModerationNote: "Оставить заметку" moderationLogs: "Журнал модерации" nUsersMentioned: "Упомянуло пользователей: {n}" securityKeyAndPasskey: "Ключ безопасности и парольная фраза" @@ -506,6 +525,8 @@ emojiStyle: "Стиль эмодзи" native: "Системные" menuStyle: "Стиль меню" style: "Стиль" +drawer: "Панель" +popup: "Всплывающие окна" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" showReactionsCount: "Видеть количество реакций на заметках" noHistory: "История пока пуста" @@ -560,9 +581,12 @@ serverLogs: "Журнал сервера" deleteAll: "Удалить всё" showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты" showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)" +withRepliesByDefaultForNewlyFollowed: "По умолчанию включайте ответы новых пользователей, на которых вы подписались, во временную шкалу" newNoteRecived: "Появилась новая заметка" +newNote: "Новая заметка" sounds: "Звуки" sound: "Звуки" +notificationSoundSettings: "Настройки звука уведомлений" listen: "Слушать" none: "Ничего" showInPage: "Показать страницу" @@ -572,6 +596,7 @@ masterVolume: "Основная регулировка громкости" notUseSound: "Выключить звук" useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен." details: "Подробнее" +renoteDetails: "Узнать больше" chooseEmoji: "Выберите эмодзи" unableToProcess: "Не удаётся завершить операцию" recentUsed: "Последние использованные" @@ -583,10 +608,12 @@ installedDate: "Дата установки" lastUsedDate: "Дата использования" state: "Состояние" sort: "Сортировать" -ascendingOrder: "по возрастанию" +ascendingOrder: "По возрастанию" descendingOrder: "По убыванию" scratchpad: "Когтеточка" scratchpadDescription: "«Когтеточка» — это место для опытов с AiScript. Здесь можно писать программы, взаимодействующие с Misskey, запускать и смотреть что из этого получается." +uiInspector: "Средство проверки пользовательского интерфейса" +uiInspectorDescription: "Вы можете просмотреть список экземпляров компонентов пользовательского интерфейса, существующих в памяти. Элементы пользовательского интерфейса генерируются с помощью серии функций Ui:C:." output: "Выходы" script: "Скрипт" disablePagesScript: "Отключить скрипты на «Страницах»" @@ -602,9 +629,9 @@ removeAllFollowingDescription: "Отменить все подписки с до userSuspended: "Эта учётная запись заморожена" userSilenced: "Этот пользователь был заглушен" yourAccountSuspendedTitle: "Эта учетная запись заблокирована" -yourAccountSuspendedDescription: "Эта учетная запись была заблокирована из-за нарушения условий предоставления услуг сервера. Свяжитесь с администратором, если вы хотите узнать более подробную причину. Пожалуйста, не создавайте новую учетную запись." +yourAccountSuspendedDescription: "Этот аккаунт нарушил ToS сервера, поэтому был заморожен. Свяжитесь с администратором, чтобы узнать подробности. Не пытайтесь создать новый аккаунт." tokenRevoked: "Токен недействителен" -tokenRevokedDescription: "Срок действия вашего токена входа истек. Пожалуйста, войдите снова." +tokenRevokedDescription: "Токен входа устарел. Пожалуйста, войдите снова." accountDeleted: "Учетная запись удалена" accountDeletedDescription: "Эта учетная запись удалена" menu: "Меню" @@ -663,18 +690,23 @@ smtpPort: "Порт" smtpUser: "Имя пользователя" smtpPass: "Пароль" emptyToDisableSmtpAuth: "Не заполняйте имя пользователя и пароль, чтобы отключить аутентификацию в SMTP." -smtpSecure: "Использовать SSL/TLS для SMTP-соединений" +smtpSecure: "Использовать SSL/TLS" smtpSecureInfo: "Выключите при использовании STARTTLS." -testEmail: "Проверка доставки электронной почты" +testEmail: "Отправить тестовое письмо" wordMute: "Скрытие слов" +wordMuteDescription: "Сведите к минимуму записи, содержащие указанное утверждение. Нажмите на свернутую запись, чтобы отобразить ее." hardWordMute: "Строгое скрытие слов" +showMutedWord: "Отображать слово без уведомления (звука)" +hardWordMuteDescription: "Скрыть заметки, содержащие указанное слово или фразу. В отличие от word mute, заметка будет полностью скрыта от просмотра." regexpError: "Ошибка в регулярном выражении" regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:" instanceMute: "Глушение инстансов" userSaysSomething: "{name} что-то сообщает" +userSaysSomethingAbout: "{name} что-то говорил о「{word}」" makeActive: "Активировать" display: "Отображение" copy: "Копировать" +copiedToClipboard: "Скопированы в буфер обмена" metrics: "Метрики" overview: "Обзор" logs: "Журналы" @@ -746,6 +778,7 @@ lockedAccountInfo: "Даже если вы вручную подтверждае alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию" loadRawImages: "Сразу показывать изображения в полном размере" disableShowingAnimatedImages: "Не проигрывать анимацию" +disableShowingAnimatedImages_caption: "Если анимации всё равно не работают, проверьте настройки специальных возможностей и режимы экономии заряда в браузере или системе" highlightSensitiveMedia: "Выделять содержимое не для всех" verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку." notSet: "Не настроено" @@ -753,7 +786,7 @@ emailVerified: "Адрес электронной почты подтвержд noteFavoritesCount: "Количество добавленного в избранное" pageLikesCount: "Количество понравившихся страниц" pageLikedCount: "Количество страниц, понравившихся другим" -contact: "Как связаться" +contact: "Почта для связи" useSystemFont: "Использовать шрифт, предлагаемый системой" clips: "Подборки" experimentalFeatures: "Экспериментальные функции" @@ -762,7 +795,6 @@ thisIsExperimentalFeature: "Это экспериментальная функц developer: "Разработчик" makeExplorable: "Опубликовать профиль в «Обзоре»." makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»." -showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте" duplicate: "Дубликат" left: "Слева" center: "По центру" @@ -770,6 +802,7 @@ wide: "Толстый" narrow: "Тонкий" reloadToApplySetting: "Это настройка вступает в силу при загрузке страницы. Перезагрузить сейчас?" needReloadToApply: "Изменения вступят в силу после перезагрузки страницы." +needToRestartServerToApply: "Для вступления изменений в силу необходимо перезапустить сервер." showTitlebar: "Показать заголовок" clearCache: "Очистить кэш" onlineUsersCount: "Пользователей сейчас в сети: {n}" @@ -812,7 +845,7 @@ showingPastTimeline: "Отображается старая лента" clear: "Очистить" markAllAsRead: "Отметить всё как прочитанное" goBack: "Выход" -unlikeConfirm: "В самом деле отменить «нравится»?" +unlikeConfirm: "В самом деле убрать «нравится»?" fullView: "Полный вид" quitFullView: "Закрыть полный вид" addDescription: "Добавить описание" @@ -840,6 +873,7 @@ administration: "Управление" accounts: "Учётные записи" switch: "Переключение" noMaintainerInformationWarning: "Не заполнены сведения об администраторах" +noInquiryUrlWarning: "URL-адрес контактной формы еще не задан." noBotProtectionWarning: "Ботозащита не настроена" configure: "Настроить" postToGallery: "Опубликовать в галерею" @@ -856,7 +890,7 @@ priority: "Приоритет" high: "Высокий" middle: "Средне" low: "Низкий" -emailNotConfiguredWarning: "Не указан адрес электронной почты" +emailNotConfiguredWarning: "Адрес почты пустует" ratio: "Соотношение" previewNoteText: "Предварительный просмотр" customCss: "Индивидуальный CSS" @@ -904,6 +938,7 @@ followersVisibility: "Видимость подписчиков" continueThread: "Показать следующие ответы" deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?" incorrectPassword: "Пароль неверен." +incorrectTotp: "Введен неверный одноразовый пароль или срок его действия истек." voteConfirm: "Отдать голос за «{choice}»?" hide: "Спрятать" useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве" @@ -928,17 +963,20 @@ oneHour: "1 час" oneDay: "1 день" oneWeek: "1 неделя" oneMonth: "1 месяц" +threeMonths: "3 месяца" +oneYear: "1 год" +threeDays: "3 дня" reflectMayTakeTime: "Изменения могут занять время для отображения" failedToFetchAccountInformation: "Не удалось получить информацию об аккаунте" rateLimitExceeded: "Ограничение скорости превышено" cropImage: "Кадрирование" -cropImageAsk: "Нужно ли кадрировать изображение?" +cropImageAsk: "Обрезать изображение?" cropYes: "Обрезать" cropNo: "Не обрезать" file: "Файлы" recentNHours: "Последние {n} ч" recentNDays: "Последние {n} сут" -noEmailServerWarning: "Почтовый сервер не установлен " +noEmailServerWarning: "Отправка писем выключена" thereIsUnresolvedAbuseReportWarning: "Остались нерешённые жалобы" recommended: "Рекомендуем" check: "Проверить" @@ -952,6 +990,7 @@ document: "Документ" numberOfPageCache: "Количество сохранённых страниц в кэше" numberOfPageCacheDescription: "Описание количества страниц в кэше" logoutConfirm: "Вы хотите выйти из аккаунта?" +logoutWillClearClientData: "Выход из аккаунта удалит настройки клиента из этого браузера. Включите автоматическое резервное копирование, чтобы иметь возможность восстановить настройки при повторном входе." lastActiveDate: "Последняя дата использования" statusbar: "Статусбар" pleaseSelect: "Пожалуйста, выберите" @@ -970,6 +1009,7 @@ failedToUpload: "Сбой выгрузки" cannotUploadBecauseInappropriate: "Файл не может быть загружен, так как было установлено, что он может содержать неприемлемое содержимое." cannotUploadBecauseNoFreeSpace: "Файл не может быть загружен, так как не осталось места на диске" cannotUploadBecauseExceedsFileSizeLimit: "Файл не может быть загружен, так как он превышает лимит размера файла." +cannotUploadBecauseUnallowedFileType: "Формат файла не подходит" beta: "Бета" enableAutoSensitive: "Автоматическое определение содержимого не для всех" enableAutoSensitiveDescription: "Позволяет определять наличие содержимого не для всех при помощи искусственного интеллекта там, где это возможно. Даже если эту опцию отключить, она всё равно может быть включена на весь инстанс." @@ -985,6 +1025,9 @@ pushNotificationAlreadySubscribed: "Push-уведомления уже вклю pushNotificationNotSupported: "Push-уведмления не поддерживаются инстансом или браузером" sendPushNotificationReadMessage: "Удалять push-уведомления когда сообщение или прочитано" sendPushNotificationReadMessageCaption: "На мгновение появится уведомление \"{emptyPushNotificationMessage}\". Расход заряда батареи может увеличиться " +pleaseAllowPushNotification: "Пожалуйста, разрешите уведомление в браузере от сайта" +browserPushNotificationDisabled: "Вы не дали разрешение на уведомления сайту" +browserPushNotificationDisabledDescription: "Разрешите уведомления в настройках браузера от {serverName}, чтобы включить PUSH уведомления" windowMaximize: "Развернуть" windowMinimize: "Свернуть" windowRestore: "Восстановить" @@ -1001,11 +1044,12 @@ neverShow: "Больше не показывать" remindMeLater: "Напомнить позже" didYouLikeMisskey: "Вам нравится Misskey?" pleaseDonate: "Сайт {host} работает на Misskey. Это бесплатное программное обеспечение, и ваши пожертвования очень бы помогли продолжать его разработку!" +correspondingSourceIsAvailable: "Соответствующий исходный код можно найти по адресу {anchor} " roles: "Роли" role: "Роль" noRole: "Нет роли" normalUser: "Обычный пользователь" -undefined: "неопределён" +undefined: "неопределённо" assign: "Назначить" unassign: "Отменить назначение" color: "Цвет" @@ -1020,6 +1064,7 @@ permissionDeniedError: "Операция запрещена" permissionDeniedErrorDescription: "У этой учетной записи нет разрешения на выполнение этой операции." preset: "Шаблоны" selectFromPresets: "Выбрать из шаблонов" +custom: "Пользовательские" achievements: "Достижения" gotInvalidResponseError: "Сервер ответил ошибкой" gotInvalidResponseErrorDescription: "Сервер временно не доступен. Возможно проводятся технические работы, или сервер отключен." @@ -1056,7 +1101,9 @@ prohibitedWords: "Запрещённые слова" prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой." prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." hiddenTags: "Скрытые хештеги" +hiddenTagsDescription: "Установленные теги не будут отображаться в тренде, можно установить несколько тегов." notesSearchNotAvailable: "Поиск заметок недоступен" +usersSearchNotAvailable: "Функция \"поиска пользователей\" отключена" license: "Лицензия" unfavoriteConfirm: "Удалить избранное?" myClips: "Мои подборки" @@ -1066,6 +1113,7 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться" enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей" enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов" +enableStatsForFederatedInstances: "Получить информацию об удаленном сервере" showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой" reactionsDisplaySize: "Размер реакций" limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере." @@ -1094,13 +1142,14 @@ vertical: "Вертикально" horizontal: "Горизонтально" position: "Позиция" serverRules: "Правила сервера" -pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями." +pleaseConfirmBelowBeforeSignup: "Прочитайте и согласитесь с информацией ниже, чтобы продолжить" pleaseAgreeAllToContinue: "Чтобы продолжить, необходимо поставить отметки во всех полях \"согласен\"." continue: "Продолжить" preservedUsernames: "Зарезервированные имена пользователей" preservedUsernamesDescription: "Перечислите зарезервированные имена пользователей, отделяя их строками. Они станут недоступны при создании учётной записи. Это ограничение не применяется при создании учётной записи администраторами. Также, уже существующие учётные записи останутся без изменений." createNoteFromTheFile: "Создать заметку из этого файла" archive: "Архив" +archived: "Архивировано" unarchive: "Разархивировать" channelArchiveConfirmTitle: "Переместить {name} в архив?" channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." @@ -1121,6 +1170,7 @@ rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно и rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый." rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Эти роли должны быть общедоступными." cancelReactionConfirm: "Вы действительно хотите удалить свою реакцию?" +changeReactionConfirm: "Вы действительно хотите удалить свою реакцию?" later: "Позже" goToMisskey: "К Misskey" additionalEmojiDictionary: "Дополнительные словари эмодзи" @@ -1128,57 +1178,204 @@ installed: "Установлено" branding: "Бренд" enableServerMachineStats: "Опубликовать характеристики сервера" enableIdenticonGeneration: "Включить генерацию иконки пользователя" +showRoleBadgesOfRemoteUsers: "Display the role badges assigned to remote users" turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность." createInviteCode: "Создать код приглашения" +createWithOptions: "Используйте параметры для создания" createCount: "Количество приглашений" +inviteCodeCreated: "Создан пригласительный код" +inviteLimitExceeded: "Достигнут предел количества пригласительных кодов, которые могут быть созданы." +createLimitRemaining: "Пригласительные коды, которые могут быть созданы: {limit} " +inviteLimitResetCycle: "За определенное {time} Вы можете создать неограниченное количество пригласительных кодов {limit} " expirationDate: "Дата истечения" noExpirationDate: "Бессрочно" +inviteCodeUsedAt: "Дата и время, когда был использован пригласительный код" +registeredUserUsingInviteCode: "Пользователи, которые использовали пригласительный код" +waitingForMailAuth: "Подтвердите вашу электронную почту" +inviteCodeCreator: "Создатель приглашения" +usedAt: "Использовано" unused: "Неиспользованное" used: "Использован" expired: "Срок действия приглашения истёк" doYouAgree: "Согласны?" +beSureToReadThisAsItIsImportant: "Это важно, поэтому, пожалуйста, прочтите это." +iHaveReadXCarefullyAndAgree: "Я прочитал(а) и согласен(сна) с условиями \"{x}" +dialog: "Диалог" icon: "Аватар" +forYou: "Для вас" +currentAnnouncements: "Текущие новости" +pastAnnouncements: "Предыдущие новости" +youHaveUnreadAnnouncements: "У вас есть непрочитанные уведомления" +useSecurityKey: "Используйте ключ безопасности или Passkey, следуя подсказкам браузера" replies: "Ответы" renotes: "Репост" loadReplies: "Показать ответы" +loadConversation: "Загрузить беседу" pinnedList: "Закреплённый список" keepScreenOn: "Держать экран включённым" +verifiedLink: "Эта ссылка принадлежит пользователю" +notifyNotes: "Оповещать о публикациях" +unnotifyNotes: "Отписаться от сообщений" +authentication: "Аутентификация" +authenticationRequiredToContinue: "Пожалуйста, пройдите аутентификацию, чтобы продолжить" +dateAndTime: "Дата и время" showRenotes: "Показывать репосты" +edited: "Изменено" +notificationRecieveConfig: "Настроить оповещения" mutualFollow: "Взаимные подписки" followingOrFollower: "Подписки или подписчики" fileAttachedOnly: "Только заметки с файлами" showRepliesToOthersInTimeline: "Показывать ответы в ленте" +hideRepliesToOthersInTimeline: "Скрыть чужие ответы в ленте" showRepliesToOthersInTimelineAll: "Показывать в ленте ответы пользователей, на которых вы подписаны" hideRepliesToOthersInTimelineAll: "Скрывать в ленте ответы пользователей, на которых вы подписаны" +confirmShowRepliesAll: "Это нельзя будет отменить. Показать ответы от всех, на кого вы подписаны?" +confirmHideRepliesAll: "Это нельзя будет отменить. Скрыть ответы от всех, на кого вы подписаны?" sourceCode: "Исходный код" sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему." repositoryUrl: "Ссылка на репозиторий" repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey" +repositoryUrlOrTarballRequired: "Если репозиторий закрыт, необходимо предоставить ссылку на tarball. Подробности см. в файле \".config/example.yml\"" +feedback: "Обратная связь" +feedbackUrl: "Ссылка для обратной связи" +impressum: "О владельце" privacyPolicy: "Политика Конфиденциальности" privacyPolicyUrl: "Ссылка на Политику Конфиденциальности" +tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" +avatarDecorations: "Украшения для аватара" attach: "Прикрепить" +detach: "Открепить" +detachAll: "Убрать всё" angle: "Угол" flip: "Переворот" -disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени" +showAvatarDecorations: "Показать украшения для аватара" +releaseToRefresh: "Отпустите, чтобы обновить" +refreshing: "Обновление..." +pullDownToRefresh: "Опустите что бы обновить" useGroupedNotifications: "Отображать уведомления сгруппировано" +emailVerificationFailedError: "Не смогли подтвердить почту. Вероятно, истек срок письма" +cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию." doReaction: "Добавить реакцию" code: "Код" +reloadRequiredToApplySettings: "Для применения настроек необходима обновить страницу." remainingN: "Остаётся: {n}" +overwriteContentConfirm: "Текущее содержимое будет перезаписано. Вы уверены?" seasonalScreenEffect: "Эффект времени года на экране" decorate: "Украсить" addMfmFunction: "Добавить MFM" +enableQuickAddMfmFunction: "Показывать расширенный выбор MFM" +bubbleGame: "BubbleGame" +sfx: "Звуковые эффекты" +soundWillBePlayed: "Будет воспроизведен звук" +showReplay: "Показать повтор" +replay: "Ответить" +endReplay: "Конец повтора" lastNDays: "Последние {n} сут" hemisphere: "Место проживания" +userSaysSomethingSensitive: "Сообщение, содержит конфиденциальные файлы от {name}" enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки" +loading: "Загрузка" surrender: "Этот пост не может быть отменен." +gameRetry: "Повторить попытку" +notUsePleaseLeaveBlank: "Если не используется, оставьте пустым" +useTotp: "Включить двухэтапную проверку" +useBackupCode: "Использовать резервные коды" +launchApp: "Запустить приложение" useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука" keepOriginalFilename: "Сохранять исходное имя файла" keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке." +noDescription: "Нет описания" alwaysConfirmFollow: "Всегда подтверждать подписку" inquiry: "Связаться" +tryAgain: "Попробуйте еще раз позже" +confirmWhenRevealingSensitiveMedia: "Спрашивать перед открытием NSFW контента" +sensitiveMediaRevealConfirm: "Возможно, это NSFW контент. Показать?" +createdLists: "Созданные списки" +createdAntennas: "Созданные антенны" +fromX: "Из {x}" +genEmbedCode: "Сгенерировать код для " +noteOfThisUser: "Список заметок этого пользователя" +clipNoteLimitExceeded: "К этому клипу больше нельзя добавить заметки" +performance: "Производительность" +modified: "Изменено" +discard: "Отменить" +thereAreNChanges: "Изменено: {n}" +signinWithPasskey: "Войдите в систему, используя свой пароль" +unknownWebAuthnKey: "Неизвестный ключ" +passkeyVerificationFailed: "Ошибка проверка ключа доступа " +passkeyVerificationSucceededButPasswordlessLoginDisabled: "Проверка Passkey выполнена, но вход без пароля отключен" messageToFollower: "Сообщение подписчикам" +testCaptchaWarning: "Эта тестовая CAPTCHA. Не используйте её!" +prohibitedWordsForNameOfUser: "Запрещенные слова (имя пользователя)" +prohibitedWordsForNameOfUserDescription: "Если имя пользователя содержит строку из этого списка, изменение имени пользователя будет запрещено. На пользователей с правами модератора это ограничение не распространяется. Имена пользователей также проверяются путём замены всех букв в нижнем регистре" +yourNameContainsProhibitedWords: "Имя, которое вы пытаетесь изменить, содержит запрещенную строку символов" +yourNameContainsProhibitedWordsDescription: "Имя содержит запрещённую строку символов. Если вы хотите использовать это имя, обратитесь к администратору сервера" +thisContentsAreMarkedAsSigninRequiredByAuthor: "Автор сообщения установил требование в виде авторизации для просмотра" +lockdown: "Доступ ограничен" +pleaseSelectAccount: "Выберите свой аккаунт" +availableRoles: "Доступные роли" +federationSpecified: "Сервер работает через белый список федерации. Связь с другими серверами ограничена" +federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." +draft: "Черновик" +draftsAndScheduledNotes: "Черновики и отложенные публикации" +confirmOnReact: "Подтверждать добавление реакции" +reactAreYouSure: "Добавить {emoji}?" +markAsSensitiveConfirm: "Отметить контент как чувствительный?" +unmarkAsSensitiveConfirm: "Снять пометку о NSFW контенте?" +preferences: "Основное" +accessibility: "Специальные возможности" +preferencesProfile: "Настройки профиля" +copyPreferenceId: "Копировать ID настройки" +resetToDefaultValue: "Сбросить настройки до стандартных" +overrideByAccount: "Переопределить этим аккаунтом" +untitled: "Без названия" +noName: "Имя не указано" +skip: "Пропустить" +syncBetweenDevices: "Синхронизировать между устройствами" postForm: "Форма отправки" +textCount: "Количество символов" information: "Описание" +inMinutes: "мин" +inDays: "сут" +schedule: "Отложить" +scheduled: "Отложено" +widgets: "Виджеты" +deviceInfo: "Об устройстве" +deviceInfoDescription: "Эта информация может быть полезна при обращении в поддержку" +youAreAdmin: "Вы администратор" +frame: "Рамки" +presets: "Шаблоны" +zeroPadding: "Без отступов" +nothingToConfigure: "Нечего менять" +_imageEditing: + _vars: + caption: "Описание файла" + filename: "Имя файла" + filename_without_ext: "Имя файла без расширения" + year: "Год создания" + month: "Месяц создания" + day: "День создания" + hour: "Час создания" + minute: "Минуты создания" + second: "Секунды создания" + camera_model: "Модель камеры" + camera_lens_model: "Модель линзы" + camera_mm: "Фокусное расстояние" + camera_mm_35: "Фокусное расстояние (экв. 35 мм)" + camera_f: "Диафрагма" + camera_s: "Выдержка" + camera_iso: "ISO" + gps_lat: "Широта" + gps_long: "Долгота" +_imageFrameEditor: + title: "Редактировать рамку" + header: "Заголовок" + footer: "Нижняя часть" + borderThickness: "Толщина рамки" + labelThickness: "Толщина границ" + font: "Шрифт" + fontSerif: "Антиква (с засечками)" + fontSansSerif: "Гротеск (без засечек)" _chat: invitations: "Пригласить" noHistory: "История пока пуста" @@ -1187,6 +1384,11 @@ _chat: send: "Отправить" _settings: webhook: "Вебхук" + preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям" + timelineAndNote: "Лента и заметки" + _chat: + showSenderName: "Показывать имя отправителя" + sendOnEnter: "Использовать Enter для отправки" _delivery: stop: "Заморожено" _type: @@ -1435,7 +1637,7 @@ _achievements: description: "Нажато здесь" _justPlainLucky: title: "Чистая удача" - description: "Может достаться с вероятностью 0,01% каждые 10 секунд." + description: "Может достаться с вероятностью 0,005% каждые 10 секунд." _setNameToSyuilo: title: "Комплекс бога" description: "Установлено «syuilo» в качестве имени" @@ -1463,6 +1665,12 @@ _achievements: title: "Brain Diver" description: "Опубликована ссылка на песню «Brain Diver»" flavor: "Мисски-Мисски Ла-Ту-Ма" + _bubbleGameExplodingHead: + title: "🤯" + description: "Самый большой объект в Bubble game" + _bubbleGameDoubleExplodingHead: + title: "Двойной🤯" + description: "Два самых больших объекта в Bubble game одновременно!" _role: new: "Новая роль" edit: "Изменить роль" @@ -1540,6 +1748,7 @@ _emailUnavailable: disposable: "Временный адрес электронной почты не принимается" mx: "Неверный почтовый сервер" smtp: "Почтовый сервер не отвечает" + banned: "Этот адрес почты недоступен" _ffVisibility: public: "Общедоступны" followers: "Показываются только подписчикам" @@ -1710,7 +1919,6 @@ _theme: buttonBg: "Фон кнопки" buttonHoverBg: "Текст кнопки" inputBorder: "Рамка поля ввода" - driveFolderBg: "Фон папки «Диска»" badge: "Значок" messageBg: "Фон беседы" fgHighlighted: "Подсвеченный текст" @@ -1801,6 +2009,7 @@ _permissions: "read:gallery-likes": "Просмотр списка понравившегося в галерее" "write:gallery-likes": "Изменение списка понравившегося в галерее" "write:admin:reset-password": "Сбросить пароль пользователю" + "write:admin:send-email": "Отправить письмо" "write:chat": "Писать и удалять сообщения" _auth: shareAccessTitle: "Разрешения для приложений" @@ -1856,6 +2065,14 @@ _widgets: chooseList: "Выберите список" clicker: "Счётчик щелчков" birthdayFollowings: "Пользователи, у которых сегодня день рождения" +_widgetOptions: + height: "Высота" + _button: + colored: "Выделена цветом" + _clock: + size: "Размер" + _birthdayFollowings: + period: "Длительность" _cw: hide: "Спрятать" show: "Показать" @@ -1898,6 +2115,9 @@ _postForm: replyPlaceholder: "Ответ на заметку..." quotePlaceholder: "Пояснение к цитате..." channelPlaceholder: "Отправить в канал" + _howToUse: + visibility_title: "Видимость" + menu_title: "Меню" _placeholders: a: "Как дела?" b: "Что интересного вокруг?" @@ -2122,6 +2342,7 @@ _abuseReport: mail: "Электронная почта" webhook: "Вебхук" _captions: + mail: "Уведомлять модераторов по почте (только при поступлении жалоб)" webhook: "Отправить уведомление Системному Вебхуку при получении или разрешении жалоб." notifiedWebhook: "Используемый Вебхук" _moderationLogTypes: @@ -2154,3 +2375,25 @@ _search: searchScopeAll: "Все" searchScopeLocal: "Местная" searchScopeUser: "Указанный пользователь" +_watermarkEditor: + opacity: "Непрозрачность" + scale: "Размер" + text: "Текст" + position: "Позиция" + type: "Тип" + image: "Изображения" + advanced: "Для продвинутых" + angle: "Угол" +_imageEffector: + _fxProps: + angle: "Угол" + scale: "Размер" + size: "Размер" + offset: "Позиция" + color: "Цвет" + opacity: "Непрозрачность" + lightness: "Осветление" +drafts: "Черновик" +_qr: + showTabTitle: "Отображение" + raw: "Текст" diff --git a/locales/si-LK.yml b/locales/si-LK.yml index c43f3d860d..841fb10585 100644 --- a/locales/si-LK.yml +++ b/locales/si-LK.yml @@ -1,10 +1,18 @@ --- _lang_: "සිංහල" monthAndDay: "{month}-{day}" +search: "සොයන්න" +reset: "යළි සකසන්න" +notifications: "දැනුම්දීම්" username: "පරිශීලක නාමය" password: "මුරපදය" +ok: "හරි" +gotIt: "තේරුණා" cancel: "අවලංගු කරන්න" +noThankYou: "එපා, ස්තුතියි" +noNotifications: "දැනුම්දීම් නැත" instance: "සර්වර්" +settings: "සැකසුම්" login: "පිවිසෙන්න" users: "පරිශීලක" note: "නෝට්" @@ -13,10 +21,19 @@ instances: "සර්වර්" smtpUser: "පරිශීලක නාමය" smtpPass: "මුරපදය" user: "පරිශීලක" +searchByGoogle: "සොයන්න" _sfx: note: "නෝට්" + notification: "දැනුම්දීම්" +_2fa: + renewTOTPCancel: "එපා, ස්තුතියි" +_widgets: + notifications: "දැනුම්දීම්" _profile: username: "පරිශීලක නාමය" _notification: _types: login: "පිවිසෙන්න" +_deck: + _columns: + notifications: "දැනුම්දීම්" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 1638fd293e..b6289f4e04 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -204,7 +204,6 @@ noUsers: "Žiadni používatelia" editProfile: "Upraviť profil" noteDeleteConfirm: "Naozaj chcete odstrániť túto poznámku?" pinLimitExceeded: "Ďalšie poznámky už nemôžete pripnúť." -intro: "Inštalácia Misskey je dokončená! Prosím vytvorte administrátora." done: "Hotovo" processing: "Pracujem..." preview: "Náhľad" @@ -682,7 +681,6 @@ experimentalFeatures: "Experimentálne funkcie" developer: "Vývojár" makeExplorable: "Spraviť účet viditeľný v \"Objavovať\"" makeExplorableDescription: "Ak toto vypnete, váš účet sa nezobrazí v sekcii \"Objavovat\"." -showGapBetweenNotesInTimeline: "Zobraziť medzeru medzi príspevkami časovej osi." duplicate: "Duplikovať" left: "Naľavo" center: "Stred" @@ -915,6 +913,17 @@ flip: "Preklopiť" lastNDays: "Posledných {n} dní" postForm: "Napísať poznámku" information: "Informácie" +inMinutes: "min" +inDays: "dní" +widgets: "Widgety" +_imageEditing: + _vars: + filename: "Názov súboru" +_imageFrameEditor: + header: "Hlavička" + font: "Písmo" + fontSerif: "Pätkové" + fontSansSerif: "Bezpätkové" _chat: invitations: "Pozvať" noHistory: "Žiadna história" @@ -1110,7 +1119,6 @@ _theme: buttonBg: "Pozadie tlačidla" buttonHoverBg: "Pozadie tlačidla (pod kurzorom)" inputBorder: "Okraj vstupného poľa" - driveFolderBg: "Pozadie priečinu disku" badge: "Odznak" messageBg: "Pozadie chatu" fgHighlighted: "Zvýraznený text" @@ -1224,6 +1232,14 @@ _widgets: aichan: "Ai" _userList: chooseList: "Vyberte zoznam" +_widgetOptions: + height: "Výška" + _button: + colored: "Farebné" + _clock: + size: "Veľkosť" + _birthdayFollowings: + period: "Trvanie" _cw: hide: "Skryť" show: "Zobraziť viac" @@ -1264,6 +1280,9 @@ _postForm: replyPlaceholder: "Odpoveď na túto poznámku..." quotePlaceholder: "Citovanie tejto poznámky..." channelPlaceholder: "Poslať do kanála..." + _howToUse: + visibility_title: "Viditeľnosť" + menu_title: "Menu" _placeholders: a: "Čo máte v pláne?" b: "Čo sa deje?" @@ -1453,3 +1472,20 @@ _remoteLookupErrors: _search: searchScopeAll: "Všetko" searchScopeLocal: "Lokálne" +_watermarkEditor: + opacity: "Priehľadnosť" + scale: "Veľkosť" + text: "Text" + type: "Typ" + image: "Obrázky" + advanced: "Rozšírené" +_imageEffector: + _fxProps: + scale: "Veľkosť" + size: "Veľkosť" + color: "Farba" + opacity: "Priehľadnosť" + lightness: "Zosvetliť" +_qr: + showTabTitle: "Zobraziť" + raw: "Text" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index ceb02ffc4c..560f4a187c 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -211,7 +211,6 @@ noUsers: "Det finns inga användare" editProfile: "Redigera profil" noteDeleteConfirm: "Är du säker på att du vill ta bort denna not?" pinLimitExceeded: "Du kan inte fästa fler noter" -intro: "Misskey har installerats! Vänligen skapa en adminanvändare." done: "Klar" processing: "Bearbetar..." preview: "Förhandsvisning" @@ -560,6 +559,9 @@ tryAgain: "Försök igen senare" signinWithPasskey: "Logga in med nyckel" unknownWebAuthnKey: "Okänd nyckel" information: "Om" +_imageEditing: + _vars: + filename: "Filnamn" _chat: invitations: "Inbjudan" members: "Medlemmar" @@ -637,6 +639,9 @@ _widgets: jobQueue: "Jobbkö" _userList: chooseList: "Välj lista" +_widgetOptions: + _clock: + size: "Storlek" _cw: hide: "Dölj" show: "Ladda mer" @@ -647,6 +652,10 @@ _poll: _visibility: home: "Hem" followers: "Följare" + specified: "Direktnoter" +_postForm: + _howToUse: + menu_title: "Meny" _profile: name: "Namn" username: "Användarnamn" @@ -693,6 +702,7 @@ _deck: list: "Listor" channel: "kanal" mentions: "Omnämningar" + direct: "Direktnoter" _webhookSettings: name: "Namn" active: "Aktiverad" @@ -712,3 +722,11 @@ _selfXssPrevention: warning: "VARNING" _search: searchScopeAll: "Allt" +_watermarkEditor: + scale: "Storlek" + image: "Bilder" +_imageEffector: + _fxProps: + scale: "Storlek" + size: "Storlek" + color: "Färg" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 06f68c85fe..48a92adae0 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -5,6 +5,7 @@ introMisskey: "ยินดีต้อนรับทุกคนจ้า! Mis poweredByMisskeyDescription: "{name} เป็นหนึ่งในเซิร์ฟเวอร์ของแพลตฟอร์มโอเพ่นซอร์ส Misskey" monthAndDay: "{month}/{day}" search: "ค้นหา" +reset: "รีเซ็ต" notifications: "เเจ้งเตือน" username: "ชื่อผู้ใช้" password: "รหัสผ่าน" @@ -48,6 +49,7 @@ pin: "ปักหมุด" unpin: "เลิกปักหมุด" copyContent: "คัดลอกเนื้อหา" copyLink: "คัดลอกลิงก์" +copyRemoteLink: "คัดลอกลิงค์ระยะไกล" copyLinkRenote: "คัดลอกลิงก์รีโน้ต" delete: "ลบ" deleteAndEdit: "ลบและแก้ไข" @@ -144,7 +146,7 @@ enterFileName: "พิมพ์ชื่อไฟล์" mute: "ปิดเสียง" unmute: "ยกเลิกการปิดเสียง" renoteMute: "ปิดเสียงรีโน้ต" -renoteUnmute: "เปิดเสียง รีโน้ต" +renoteUnmute: "เลิกปิดเสียงรีโน้ต" block: "บล็อก" unblock: "เลิกบล็อก" suspend: "ระงับ" @@ -218,6 +220,7 @@ silenceThisInstance: "ปิดปากเซิร์ฟเวอร์นี mediaSilenceThisInstance: "ปิดปากสื่อของเซิร์ฟเวอร์นี้" operations: "ดำเนินการ" software: "ซอฟต์แวร์" +softwareName: "ชื่อซอฟต์แวร์" version: "เวอร์ชั่น" metadata: "Metadata" withNFiles: "{n} ไฟล์" @@ -239,8 +242,8 @@ silencedInstances: "ปิดปากเซิร์ฟเวอร์นี้ silencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปาก คั่นด้วยการขึ้นบรรทัดใหม่, บัญชีทั้งหมดของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปากเช่นกัน ทำได้เฉพาะคำขอติดตามเท่านั้น และไม่สามารถกล่าวถึงบัญชีในเซิร์ฟเวอร์นี้ได้หากไม่ได้ถูกติดตามกลับ | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก" mediaSilencedInstances: "เซิร์ฟเวอร์ที่ถูกปิดปากสื่อ" mediaSilencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปากสื่อ คั่นด้วยการขึ้นบรรทัดใหม่, ไฟล์ที่ถูกส่งจากบัญชีของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปาก แล้วจะถูกติดเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน และเอโมจิแบบกำหนดเองก็จะใช้ไม่ได้ด้วย | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก" -federationAllowedHosts: "เซิร์ฟเวอร์ที่เปิดให้บริการแบบเฟเดอเรชั่น" -federationAllowedHostsDescription: "ระบุชื่อโฮสต์ของเซิร์ฟเวอร์ที่คุณต้องการอนุญาตให้เชื่อมต่อแบบเฟเดอเรชั่น โดยต้องเว้นวรรคแต่ละบรรทัด" +federationAllowedHosts: "เซิร์ฟเวอร์ที่อนุญาตให้เชื่อมกับสหพันธ์" +federationAllowedHostsDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่อนุญาตให้เชื่อมกับสหพันธ์ โดยแยกแต่ละรายการด้วยบรรทัดใหม่" muteAndBlock: "ปิดเสียงและบล็อก" mutedUsers: "ผู้ใช้ที่ถูกปิดเสียง" blockedUsers: "ผู้ใช้ที่ถูกบล็อก" @@ -248,9 +251,9 @@ noUsers: "ไม่พบผู้ใช้งาน" editProfile: "แก้ไขโปรไฟล์" noteDeleteConfirm: "ต้องการลบโน้ตนี้ใช่ไหม?" pinLimitExceeded: "คุณไม่สามารถปักหมุดโน้ตเพิ่มเติมใดๆได้อีก" -intro: "การติดตั้ง Misskey เสร็จสิ้นแล้วนะ! โปรดสร้างผู้ใช้งานที่เป็นผู้ดูแลระบบ" done: "เสร็จสิ้น" processing: "กำลังประมวลผล..." +preprocessing: "กำลังจัดเตรียม..." preview: "แสดงตัวอย่าง" default: "ค่าเริ่มต้น" defaultValueIs: "ค่าเริ่มต้น: {value}" @@ -296,9 +299,11 @@ uploadFromUrl: "อัปโหลดจาก URL" uploadFromUrlDescription: "URL ของไฟล์ที่คุณต้องการอัปโหลด" uploadFromUrlRequested: "ร้องขอการอัปโหลดแล้ว" uploadFromUrlMayTakeTime: "การอัปโหลดอาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์" +uploadNFiles: "อัปโหลด {n} ไฟล์" explore: "สำรวจ" messageRead: "อ่านแล้ว" noMoreHistory: "ไม่มีประวัติเพิ่มเติม" +startChat: "เริ่มแชต" nUsersRead: "อ่านโดย {n}" agreeTo: "ฉันยอมรับ {0}" agree: "ยอมรับ" @@ -323,6 +328,7 @@ dark: "มืด" lightThemes: "ธีมสว่าง" darkThemes: "ธีมมืด" syncDeviceDarkMode: "ซิงค์โหมดมืดกับการตั้งค่าอุปกรณ์ของคุณ" +switchDarkModeManuallyWhenSyncEnabledConfirm: "“{x}” เปิดอยู่ ต้องการปิดการซิงค์และสลับโหมดด้วยตนเองหรือไม่?" drive: "ไดรฟ์" fileName: "ชื่อไฟล์" selectFile: "เลือกไฟล์" @@ -363,7 +369,7 @@ reject: "ปฏิเสธ" normal: "ปกติ" instanceName: "ชื่อเซิร์ฟเวอร์" instanceDescription: "คำอธิบายแนะนำเซิร์ฟเวอร์" -maintainerName: "ผู้ดูแล" +maintainerName: "ชื่อผู้ดูแลระบบ" maintainerEmail: "อีเมลผู้ดูแลระบบ" tosUrl: "URL เงื่อนไขการให้บริการ" thisYear: "ปีนี้" @@ -421,6 +427,7 @@ antennaExcludeBots: "ยกเว้นบัญชีบอต" antennaKeywordsDescription: "คั่นด้วยเว้นวรรคสำหรับเงื่อนไข AND, หรือขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR" notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่" withFileAntenna: "เฉพาะโน้ตที่มีไฟล์" +excludeNotesInSensitiveChannel: "ไม่รวมโน้ตจากช่องเนื้อหาละเอียดอ่อน" enableServiceworker: "เปิดใช้งานการแจ้งเตือนแบบพุชไปยังเบราว์เซอร์ของคุณ" antennaUsersDescription: "ระบุหนึ่งชื่อผู้ใช้ต่อบรรทัด" caseSensitive: "อักษรพิมพ์ใหญ่-พิมพ์เล็กความหมายต่างกัน" @@ -451,17 +458,17 @@ totpDescription: "ใช้แอปยืนยันตัวตนเพื moderator: "ผู้ควบคุม" moderation: "การกลั่นกรอง" moderationNote: "โน้ตการกลั่นกรอง" -moderationNoteDescription: "คุณสามารถใส่โน้ตส่วนตัวที่เฉพาะผู้ดูแลระบบเท่านั้นที่สามารถเข้าถึงได้" +moderationNoteDescription: "สามารถจดเมโมที่จะแบ่งปันเฉพาะระหว่างผู้ควบคุมได้" addModerationNote: "เพิ่มโน้ตการกลั่นกรอง" moderationLogs: "ปูมการควบคุมดูแล" nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} ราย" -securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน" -securityKey: "กุญแจความปลอดภัย" +securityKeyAndPasskey: "Security key และ Passkey" +securityKey: "Security Key" lastUsed: "ใช้ล่าสุด" lastUsedAt: "ใช้งานครั้งล่าสุด: {t}" unregister: "เลิกติดตาม" passwordLessLogin: "เข้าสู่ระบบแบบไม่ใช้รหัสผ่าน" -passwordLessLoginDescription: "อนุญาตให้เข้าสู่ระบบโดยไม่ต้องใช้รหัสผ่านโดยใช้รหัสรักษาความปลอดภัยหรือรหัสผ่านเท่านั้น" +passwordLessLoginDescription: "เข้าสู่ระบบโดยไม่ใช้รหัสผ่าน โดยใช้เฉพาะ Security Key หรือ Passkey เท่านั้น" resetPassword: "รีเซ็ตรหัสผ่าน" newPasswordIs: "รหัสผ่านใหม่คือ “{password}”" reduceUiAnimation: "ลดภาพเคลื่อนไหว UI" @@ -571,8 +578,10 @@ showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ showFixedPostFormInChannel: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนของไทม์ไลน์ (ช่อง)" withRepliesByDefaultForNewlyFollowed: "แสดงการตอบกลับจากผู้ใช้ที่คุณเพิ่งติดตามลงไทม์ไลน์ตามค่าเริ่มต้น" newNoteRecived: "มีโน้ตใหม่" +newNote: "โน้ตใหม่" sounds: "เสียง" sound: "เสียง" +notificationSoundSettings: "ตั้งค่าเสียงแจ้งเตือน" listen: "ฟัง" none: "ไม่มี" showInPage: "แสดงในเพจ" @@ -582,6 +591,7 @@ masterVolume: "ระดับเสียงหลัก" notUseSound: "ไม่ใช้เสียง" useSoundOnlyWhenActive: "มีเสียงออกเฉพาะตอนกำลังใช้ Misskey อยู่เท่านั้น" details: "รายละเอียด" +renoteDetails: "รายละเอียดรีโน้ต" chooseEmoji: "เลือกเอโมจิ" unableToProcess: "ไม่สามารถดำเนินการให้เสร็จสิ้นได้" recentUsed: "ใช้ล่าสุด" @@ -603,8 +613,8 @@ output: "เอาท์พุต" script: "สคริปต์" disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ" updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งานระยะไกล" -unsetUserAvatar: "เลิกตั้งอวตาร" -unsetUserAvatarConfirm: "ต้องการเลิกตั้งอวตารใข่ไหม?" +unsetUserAvatar: "เลิกตั้งไอคอน" +unsetUserAvatarConfirm: "ต้องการเลิกตั้งไอคอนประจำตัวหรือไม่?" unsetUserBanner: "เลิกตั้งแบนเนอร์" unsetUserBannerConfirm: "ต้องการเลิกตั้งแบนเนอร์?" deleteAllFiles: "ลบไฟล์ทั้งหมด" @@ -679,14 +689,19 @@ smtpSecure: "ใช้โดยนัย SSL/TLS สำหรับการเ smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS" testEmail: "ทดสอบการส่งอีเมล" wordMute: "ปิดเสียงคำ" +wordMuteDescription: "ย่อโน้ตที่มีวลีที่ระบุ สามารถดูโน้ตที่ย่อแล้วได้โดยคลิกที่โน้ตเหล่านั้น" hardWordMute: "ปิดเสียงคำแบบแข็งโป๊ก" +showMutedWord: "แสดงคำที่ถูกปิดเสียง" +hardWordMuteDescription: "จะซ่อนโน้ตที่มีคำที่ระบุไว้ ซึ่งไม่เหมือนการปิดเสียงคำ ในกรณีนี้โน้ตจะไม่แสดงเลย" regexpError: "เกิดข้อผิดพลาดใน regular expression" regexpErrorDescription: "เกิดข้อผิดพลาดใน regular expression บรรทัดที่ {line} ของการปิดเสียงคำ {tab} :" instanceMute: "ปิดเสียงเซิร์ฟเวอร์" userSaysSomething: "{name} พูดอะไรบางอย่าง" +userSaysSomethingAbout: "{name} พูดบางอย่างเกี่ยวกับ “{word}”" makeActive: "เปิดใช้งาน" display: "แสดงผล" copy: "คัดลอก" +copiedToClipboard: "คัดลอกไปยังคลิปบอร์ดแล้ว" metrics: "เมตริก" overview: "ภาพรวม" logs: "ปูม" @@ -752,7 +767,7 @@ yes: "ใช่" no: "ไม่" driveFilesCount: "จำนวนไฟล์ไดรฟ์" driveUsage: "การใช้พื้นที่ไดรฟ์" -noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล" +noCrawle: "ปฏิเสธการจัดทำดัชนีของ Crawler (โปรแกรมรวบรวมข้อมูล)" noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ โน้ต หน้าเพจ ฯลฯ" lockedAccountInfo: "แม้ว่าการอนุมัติการติดตามถูกเปิดใช้งานอยู่ทุกคนก็ยังคงสามารถเห็นโน้ตของคุณได้ เว้นแต่ว่าคุณจะเปลี่ยนการเปิดเผยโน้ตของคุณเป็น “เฉพาะผู้ติดตาม”" alwaysMarkSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนเป็นค่าเริ่มต้น" @@ -762,7 +777,7 @@ highlightSensitiveMedia: "ไฮไลท์สื่อที่มีเนื verificationEmailSent: "ได้ส่งอีเมลยืนยันแล้ว กรุณาเข้าลิงก์ที่ระบุในอีเมลเพื่อทำการตั้งค่าให้เสร็จสิ้น" notSet: "ไม่ได้ตั้งค่า" emailVerified: "อีเมลได้รับการยืนยันแล้ว" -noteFavoritesCount: "จำนวนโน้ตที่ชื่นชอบ" +noteFavoritesCount: "จำนวนโน้ตโปรด" pageLikesCount: "จำนวนเพจที่ถูกใจ" pageLikedCount: "จำนวนการกดถูกใจเพจที่ได้รับแล้ว" contact: "ติดต่อ" @@ -774,7 +789,6 @@ thisIsExperimentalFeature: "นี่เป็นฟีเจอร์ทดล developer: "สำหรับนักพัฒนา" makeExplorable: "ทำให้บัญชีมองเห็นใน “สำรวจ”" makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน “สำรวจ”" -showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์" duplicate: "ทำซ้ำ" left: "ซ้าย" center: "กึ่งกลาง" @@ -782,6 +796,7 @@ wide: "กว้าง" narrow: "ชิด" reloadToApplySetting: "การตั้งค่านี้จะมีผลหลังจากโหลดหน้าซ้ำเท่านั้น ต้องการที่จะโหลดใหม่เลยไหม?" needReloadToApply: "ต้องรีโหลดเพื่อให้การเปลี่ยนแปลงมีผล" +needToRestartServerToApply: "จำเป็นต้องรีสตาร์ทเซิร์ฟเวอร์เพื่อให้การเปลี่ยนแปลงมีผล" showTitlebar: "แสดงแถบชื่อ" clearCache: "ล้างแคช" onlineUsersCount: "{n} รายกำลังออนไลน์" @@ -875,7 +890,7 @@ previewNoteText: "แสดงตัวอย่าง" customCss: "CSS ที่กำหนดเอง" customCssWarn: "ควรใช้การตั้งค่านี้เฉพาะต่อเมื่อคุณรู้มันใช้ทำอะไร การตั้งค่าที่ไม่เหมาะสมอาจทำให้ไคลเอ็นต์ไม่สามารถใช้งานได้อย่างถูกต้อง" global: "ทั่วโลก" -squareAvatars: "แสดงผลอวตารเป็นสี่เหลี่ยม" +squareAvatars: "แสดงไอคอนประจำตัวเป็นสี่เหลี่ยม" sent: "ส่ง" received: "ได้รับแล้ว" searchResult: "ผลการค้นหา" @@ -942,6 +957,9 @@ oneHour: "1 ชั่วโมง" oneDay: "1 วัน" oneWeek: "1 สัปดาห์" oneMonth: "หนึ่งเดือน" +threeMonths: "3 เดือน" +oneYear: "1 ปี" +threeDays: "3 วัน" reflectMayTakeTime: "อาจจำเป็นต้องใช้เวลาสักระยะหนึ่งจึงจะเห็นแสดงผลได้นะ" failedToFetchAccountInformation: "ไม่สามารถเรียกดึงข้อมูลบัญชีได้" rateLimitExceeded: "เกินขีดจำกัดอัตรา" @@ -966,6 +984,7 @@ document: "เอกสาร" numberOfPageCache: "จำนวนหน้าเพจที่แคช" numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย" logoutConfirm: "ต้องการออกจากระบบใช่ไหม?" +logoutWillClearClientData: "เมื่อออกจากระบบ ข้อมูลการตั้งค่าของไคลเอนต์จะถูกลบออกจากเบราว์เซอร์ เพื่อให้สามารถกู้คืนข้อมูลการตั้งค่าได้เมื่อกลับมาเข้าสู่ระบบอีกครั้ง โปรดเปิดใช้งานการสำรองข้อมูลการตั้งค่าอัตโนมัติ" lastActiveDate: "ใช้งานล่าสุดเมื่อ" statusbar: "แถบสถานะ" pleaseSelect: "ตัวเลือก" @@ -984,6 +1003,7 @@ failedToUpload: "การอัปโหลดล้มเหลว" cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW" cannotUploadBecauseNoFreeSpace: "ไม่สามารถอัปโหลดได้เนื่องจากไม่มีพื้นที่ว่างในไดรฟ์เหลือแล้ว" cannotUploadBecauseExceedsFileSizeLimit: "ไม่สามารถอัปโหลดไฟล์นี้ได้แล้วเนื่องจากเกินขีดจำกัดของขนาดไฟล์แล้ว" +cannotUploadBecauseUnallowedFileType: "ไม่สามารถอัปโหลดได้เนื่องจากเป็นชนิดไฟล์ที่ไม่ได้รับอนุญาต" beta: "เบต้า" enableAutoSensitive: "ทำเครื่องหมายว่ามีเนื้อหาที่ละเอียดอ่อนโดยอัตโนมัติ" enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อว่ามีเนื้อหาโดยละเอียดอ่อนโดยอัตโนมัติ ผ่าน Machine Learning หากเป็นไปได้ แม้ว่าคุณจะปิดคุณสมบัตินี้ ก็อาจถูกตั้งค่าโดยอัตโนมัติ ทั้งนี้ขึ้นอยู่กับเซิร์ฟเวอร์" @@ -1003,7 +1023,7 @@ windowMaximize: "ขยายใหญ่สุด" windowMinimize: "ย่อเล็กที่สุด" windowRestore: "เลิกทำ" caption: "คำอธิบาย" -loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้" +loggedInAsBot: "เข้าสู่ระบบเป็นบอตอยู่ในขณะนี้" tools: "เครื่องมือ" cannotLoad: "ไม่สามารถโหลดได้" numberOfProfileView: "มุมมองโปรไฟล์" @@ -1035,6 +1055,7 @@ permissionDeniedError: "การดำเนินถูกปฏิเสธ" permissionDeniedErrorDescription: "บัญชีนี้ไม่มีสิทธิ์อนุญาตในการดำเนินการนี้" preset: "พรีเซ็ต" selectFromPresets: "เลือกจากการพรีเซ็ต" +custom: "แบบกำหนดเอง" achievements: "ความสำเร็จ" gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง" gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ" @@ -1052,7 +1073,7 @@ exploreOtherServers: "มองหาเซิร์ฟเวอร์อื่ letsLookAtTimeline: "มาดูไทม์ไลน์กัน" disableFederationConfirm: "ปิดใช้งานสหพันธ์เลยใช่ไหม?" disableFederationConfirmWarn: "โพสต์จะยังคงเป็นสาธารณะต่อไป เว้นแต่จะตั้งค่าเป็นอย่างอื่น" -disableFederationOk: "ปิดการใช้งาน" +disableFederationOk: "ปิดการใช้งานสหพันธ์" invitationRequiredToRegister: "เซิร์ฟเวอร์นี้เป็นแบบรับเชิญ เฉพาะผู้มีรหัสเชิญเท่านั้นถึงสามารถลงทะเบียนได้" emailNotSupported: "เซิร์ฟเวอร์นี้ไม่รองรับการส่งอีเมล" postToTheChannel: "โพสต์ลงช่อง" @@ -1073,6 +1094,7 @@ prohibitedWordsDescription2: "ถ้าแยกด้วยเว้นวร hiddenTags: "แฮชแท็กที่ซ่อนอยู่" hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่" notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน" +usersSearchNotAvailable: "การค้นหาผู้ใช้ไม่พร้อมใช้งาน" license: "ใบอนุญาต" unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?" myClips: "คลิปของฉัน" @@ -1082,7 +1104,7 @@ retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ" enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล" enableChartsForFederatedInstances: "สร้างแผนภูมิของเซิร์ฟเวอร์ระยะไกล" -enableStatsForFederatedInstances: "ดึงข้อมูลสถิติจากเซิร์ฟเวอร์ที่อยู่ห่างไกล" +enableStatsForFederatedInstances: "ดึงข้อมูลจากเซิร์ฟเวอร์ระยะไกล" showClipButtonInNoteFooter: "เพิ่ม “คลิป” ไปยังเมนูสั่งการของโน้ต" reactionsDisplaySize: "ขนาดของรีแอคชั่น" limitWidthOfReaction: "จำกัดความกว้างสูงสุดของรีแอคชั่นและแสดงให้เล็กลง" @@ -1213,19 +1235,18 @@ impressumDescription: "การติดป้ายกำกับ (Impressum) privacyPolicy: "นโยบายความเป็นส่วนตัว" privacyPolicyUrl: "URL นโยบายความเป็นส่วนตัว" tosAndPrivacyPolicy: "เงื่อนไขในการให้บริการและนโยบายความเป็นส่วนตัว" -avatarDecorations: "การตกแต่งอวตาร" +avatarDecorations: "ของตกแต่งไอคอน" attach: "แนบ" detach: "นำออก" detachAll: "เอาออกทั้งหมด" angle: "แองเกิล" flip: "พลิก" -showAvatarDecorations: "แสดงตกแต่งอวตาร" +showAvatarDecorations: "แสดงของตกแต่งไอคอน" releaseToRefresh: "ปล่อยเพื่อรีเฟรช" refreshing: "กำลังรีเฟรช..." pullDownToRefresh: "ดึงลงเพื่อรีเฟรช" -disableStreamingTimeline: "ปิดใช้งานอัปเดตไทม์ไลน์แบบเรียลไทม์" useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว" -signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว" +emailVerificationFailedError: "เกิดปัญหาในขณะตรวจสอบอีเมล อาจเป็นไปได้ว่าลิงก์หมดอายุแล้ว" cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย" doReaction: "เพิ่มรีแอคชั่น" code: "โค้ด" @@ -1276,35 +1297,249 @@ clipNoteLimitExceeded: "ไม่สามารถเพิ่มโน้ต performance: "ประสิทธิภาพ​" modified: "แก้ไข" discard: "ละทิ้ง" -thereAreNChanges: "มีอยู่ {n} เปลี่ยนแปลง(s)" +thereAreNChanges: "มีการเปลี่ยนแปลง {n} รายการ" signinWithPasskey: "ลงชื่อเข้าใช้ด้วย Passkey" -unknownWebAuthnKey: "พาสคีย์ไม่ถูกต้องค่ะ" -passkeyVerificationFailed: "การยืนยันกุญแจดิจิทัลไม่สำเร็จค่ะ" -passkeyVerificationSucceededButPasswordlessLoginDisabled: "การยืนยันพาสคีย์สำเร็จแล้ว แต่การลงชื่อเข้าใช้แบบไม่ต้องใส่รหัสผ่านถูกปิดใช้งานแล้ว" +unknownWebAuthnKey: "เป็น Passkey ที่ยังไม่ได้ลงทะเบียน" +passkeyVerificationFailed: "การยืนยัน Passkey ล้มเหลว" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "การยืนยัน Passkey สำเร็จ แต่การเข้าสู่ระบบแบบไม่ใช้รหัสผ่านถูกปิดใช้งานอยู่" messageToFollower: "ข้อความถึงผู้ติดตาม" target: "เป้า" testCaptchaWarning: "ฟังก์ชันนี้มีไว้สำหรับทดสอบ CAPTCHA เท่านั้น\nห้ามนำไปใช้ในระบบจริงโดยเด็ดขาด" prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช้เป็นชื่อผู้ใช้ได้" -prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ" +prohibitedWordsForNameOfUserDescription: "จะไม่อนุญาตให้เปลี่ยนชื่อผู้ใช้หากชื่อของผู้ใช้มีข้อความที่อยู่ในรายการนี้ แต่ผู้ใช้ที่มีสิทธิ์เป็นผู้ควบคุมจะไม่ได้รับผลกระทบจากข้อจำกัดนี้" yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม" yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ" +thisContentsAreMarkedAsSigninRequiredByAuthor: "ผู้โพสต์ได้ตั้งค่าว่าต้องเข้าสู่ระบบจึงจะสามารถดูได้" +lockdown: "ล็อกดาวน์" +pleaseSelectAccount: "โปรดเลือกบัญชี" +availableRoles: "บทบาทที่ใช้ได้" +acknowledgeNotesAndEnable: "เปิดใช้งานหลังจากที่เข้าใจข้อควรระวังแล้ว" +federationSpecified: "เซิร์ฟเวอร์นี้ดำเนินงานในระบบกลุ่มไวท์ลิสต์ ไม่สามารถติดต่อกับเซิร์ฟเวอร์อื่นที่ไม่ได้รับอนุญาตจากผู้ดูแลระบบได้" +federationDisabled: "เซิร์ฟเวอร์นี้ปิดใช้งานสหพันธ์ ไม่สามารถติดต่อหรือแลกเปลี่ยนข้อมูลกับผู้ใช้จากเซิร์ฟเวอร์อื่นได้" +draft: "ร่าง" +draftsAndScheduledNotes: "ร่างและกำหนดเวลาโพสต์" +confirmOnReact: "ยืนยันเมื่อทำการรีแอคชั่น" +reactAreYouSure: "ต้องการใส่รีแอคชั่นด้วย \"{emoji}\" หรือไม่?" +markAsSensitiveConfirm: "ต้องการตั้งค่าสื่อนี้ว่าเป็นเนื้อหาละเอียดอ่อนหรือไม่?" +unmarkAsSensitiveConfirm: "ต้องการยกเลิกการระบุว่าสื่อนี้มีเนื้อหาละเอียดอ่อนหรือไม่?" +preferences: "การตั้งค่าสภาพแวดล้อม" +accessibility: "การช่วยการเข้าถึง" +preferencesProfile: "โปรไฟล์การกำหนดค่า" +copyPreferenceId: "คัดลือก ID การตั้งค่า" +resetToDefaultValue: "คืนค่าเป็นค่าเริ่มต้น" +overrideByAccount: "เขียนทับด้วยบัญชี" +untitled: "ไม่มีชื่อ" +noName: "ไม่มีชื่อ" +skip: "ข้าม" +restore: "กู้คืน" +syncBetweenDevices: "ซิงค์ระหว่างอุปกรณ์" +preferenceSyncConflictTitle: "การตั้งค่ามีอยู่บนเซิร์ฟเวอร์" +preferenceSyncConflictText: "การตั้งค่าที่เปิดใช้งานการซิงค์จะบันทึกค่าลงในเซิร์ฟเวอร์ อย่างไรก็ดี พบว่ามีค่าการตั้งค่านี้ที่เคยบันทึกไว้ในเซิร์ฟเวอร์แล้ว ต้องการดำเนินการอย่างไร?" +preferenceSyncConflictChoiceMerge: "รวมเข้าด้วยกัน" +preferenceSyncConflictChoiceServer: "เขียนทับด้วยค่าการตั้งค่าเซิร์ฟเวอร์" +preferenceSyncConflictChoiceDevice: "เขียนทับด้วยค่าการตั้งค่าอุปกรณ์" +preferenceSyncConflictChoiceCancel: "ยกเลิกการเปิดใช้งานการซิงค์" +paste: "วาง" +emojiPalette: "จานสีเอโมจิ" postForm: "แบบฟอร์มการโพสต์" +textCount: "จำนวนอักขระ" information: "เกี่ยวกับ" +chat: "แชต" +directMessage: "แชตเลย" +directMessage_short: "ข้อความ" +migrateOldSettings: "ย้ายข้อมูลการตั้งค่าเก่า" +migrateOldSettings_description: "โดยปกติจะทำโดยอัตโนมัติ แต่หากด้วยเหตุผลบางประการที่ไม่สามารถย้ายได้สำเร็จ สามารถสั่งย้ายด้วยตนเองได้ การตั้งค่าปัจจุบันจะถูกเขียนทับ" +compress: "บีบอัด" +right: "ขวา" +bottom: "ภายใต้" +top: "บน" +embed: "ฝัง" +settingsMigrating: "กำลังย้ายการตั้งค่า กรุณารอสักครู่... (สามารถย้ายด้วยตนเองภายหลังได้ที่ การตั้งค่า → อื่นๆ → ย้ายข้อมูลการตั้งค่าเก่า)" +readonly: "อ่านได้อย่างเดียว" +goToDeck: "กลับไปยังเด็ค" +federationJobs: "งานสหพันธ์" +driveAboutTip: "ในไดรฟ์จะแสดงรายการไฟล์ที่เคยอัปโหลดไว้ก่อนหน้า
\nสามารถนำมาใช้ซ้ำเมื่อแนบไฟล์ในโน้ต หรือตั้งค่าให้อัปโหลดไฟล์ล่วงหน้าเพื่อนำไปโพสต์ทีหลังได้
\nโปรดระวัง เมื่อลบไฟล์ ไฟล์นั้นจะไม่แสดงในทุกที่ที่เคยใช้ไฟล์นี้ (โน้ต, หน้าเพจ, อวตาร, แบนเนอร์ ฯลฯ)
\nสามารถสร้างโฟลเดอร์เพื่อจัดระเบียบได้" +scrollToClose: "เลื่อนเพื่อปิด" +advice: "คำแนะนำ" +realtimeMode: "โหมดเรียลไทม์" +turnItOn: "เปิดใช้งาน" +turnItOff: "ปิดใช้งาน" +emojiMute: "ปิดเสียงเอโมจิ" +emojiUnmute: "เลิกปิดเสียงเอโมจิ" +muteX: "ปิดเสียง {x}" +unmuteX: "เลิกปิดเสียง {x}" +abort: "หยุดและยกเลิก" +tip: "คำแนะนำและเคล็ดลับ" +redisplayAllTips: "แสดงคำแนะนำและเคล็ดลับทั้งหมดอีกครั้ง" +hideAllTips: "ซ่อนคำแนะนำและเคล็ดลับทั้งหมด" +defaultImageCompressionLevel: "ค่าการบีบอัดภาพเริ่มต้น" +defaultImageCompressionLevel_description: "หากตั้งค่าต่ำ จะรักษาคุณภาพภาพได้ดีขึ้นแต่ขนาดไฟล์จะเพิ่มขึ้น
หากตั้งค่าสูง จะลดขนาดไฟล์ได้ แต่คุณภาพภาพจะลดลง" +defaultCompressionLevel: "ค่าการบีบอัดเริ่มต้น" +defaultCompressionLevel_description: "ถ้าต่ำ จะรักษาคุณภาพได้ แต่ขนาดไฟล์จะเพิ่มขึ้น
ถ้าสูง จะลดขนาดไฟล์ได้ แต่คุณภาพจะลดลง" +inMinutes: "นาที" +inDays: "วัน" +safeModeEnabled: "โหมดปลอดภัยถูกเปิดใช้งาน" +pluginsAreDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน ปลั๊กอินทั้งหมดจึงถูกปิดใช้งาน" +customCssIsDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน CSS แบบกำหนดเองจึงไม่ได้ถูกนำมาใช้" +themeIsDefaultBecauseSafeMode: "ในระหว่างที่โหมดปลอดภัยถูกเปิดใช้งาน จะใช้ธีมเริ่มต้น เมื่อปิดโหมดปลอดภัยจะกลับคืนดังเดิม" +thankYouForTestingBeta: "ขอบคุณที่ให้ความร่วมมือในการทดสอบเวอร์ชันเบต้า!" +createUserSpecifiedNote: "สร้างโน้ตแบบไดเร็กต์" +schedulePost: "กำหนดเวลาให้โพสต์" +scheduleToPostOnX: "กำหนดเวลาให้โพสต์ไว้ที่ {x}" +scheduledToPostOnX: "มีการกำหนดเวลาให้โพสต์ไว้ที่ {x}" +schedule: "กำหนดเวลา" +scheduled: "กำหนดเวลา" +widgets: "วิดเจ็ต" +presets: "พรีเซ็ต" +_imageEditing: + _vars: + filename: "ชื่อไฟล์" +_imageFrameEditor: + header: "ส่วนหัว" + withQrCode: "QR โค้ด" + font: "แบบอักษร" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "ต้องการออกโดยไม่บันทึกหรือไม่?" +_compression: + _quality: + high: "คุณภาพสูง" + medium: "คุณภาพปานกลาง" + low: "คุณภาพต่ำ" + _size: + large: "ขนาดใหญ่" + medium: "ขนาดปานกลาง" + small: "ขนาดเล็ก" +_order: + newest: "เรียงจากใหม่ไปเก่า" + oldest: "เรียงจากเก่าไปใหม่" _chat: + messages: "ข้อความ" + noMessagesYet: "ยังไม่มีข้อความ" + newMessage: "ข้อความใหม่" + individualChat: "แชตส่วนตัว" + individualChat_description: "สามารถแชตแบบตัวต่อตัวกับผู้ใช้ที่ระบุไว้ได้" + roomChat: "ห้องแชต" + roomChat_description: "สามารถแชตแบบกลุ่มหลายคนได้\nและสามารถแชตกับผู้ใช้ที่ไม่ได้อนุญาตแชตส่วนตัวได้ หากอีกฝ่ายยอมรับ" + createRoom: "สร้างห้อง" + inviteUserToChat: "เชิญผู้ใช้และเริ่มแชตได้เลย" + yourRooms: "ห้องที่สร้างไว้" + joiningRooms: "ห้องที่เข้าร่วมอยู่" invitations: "คำเชิญ" + noInvitations: "ไม่มีคำเชิญ" + history: "ประวัติ" noHistory: "ไม่มีประวัติ" + noRooms: "ไม่มีห้อง" + inviteUser: "เชิญผู้ใช้" + sentInvitations: "คำเชิญที่ส่งไปแล้ว" + join: "เข้าร่วม" + ignore: "ไม่สนใจ" + leave: "ออกจากห้อง" members: "สมาชิก" + searchMessages: "ค้นหาข้อความ" home: "หน้าหลัก" send: "ส่ง" + newline: "ขึ้นบรรทัดใหม่" + muteThisRoom: "ปิดเสียงห้องนี้" + deleteRoom: "ลบห้อง" + chatNotAvailableForThisAccountOrServer: "แชตไม่ได้เปิดใช้งานบนเซิร์ฟเวอร์นี้ หรือบัญชีนี้" + chatIsReadOnlyForThisAccountOrServer: "แชตบนเซิร์ฟเวอร์นี้ หรือบัญชีนี้ เป็นแบบอ่านอย่างเดียว ไม่สามารถส่งข้อความใหม่ สร้างหรือเข้าร่วมห้องแชตได้" + chatNotAvailableInOtherAccount: "บัญชีคู่สนทนาไม่สามารถใช้ฟังก์ชันแชตได้" + cannotChatWithTheUser: "ไม่สามารถเริ่มแชตกับผู้ใช้นี้ได้" + cannotChatWithTheUser_description: "แชตใช้งานไม่ได้ หรือคู่สนทนายังไม่ได้เปิดแชต" + youAreNotAMemberOfThisRoomButInvited: "คุณไม่ได้เป็นผู้เข้าร่วมห้องนี้ แต่มีคำเชิญส่งมา หากต้องการเข้าร่วม กรุณายืนยันคำเชิญ" + doYouAcceptInvitation: "ต้องการยอมรับคำเชิญหรือไม่?" + chatWithThisUser: "แชตเลย" + thisUserAllowsChatOnlyFromFollowers: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ติดตามเท่านั้น" + thisUserAllowsChatOnlyFromFollowing: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ที่เขาติดตามเท่านั้น" + thisUserAllowsChatOnlyFromMutualFollowing: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ที่ติดตามซึ่งกันและกันทั้งสองฝ่ายเท่านั้น" + thisUserNotAllowedChatAnyone: "ผู้ใช้นี้ไม่รับแชตจากใครเลย" + chatAllowedUsers: "ผู้ที่อนุญาตให้แชตด้วย" + chatAllowedUsers_note: "ไม่ว่าจะตั้งค่ายังไง คุณยังสามารถแชตกับคนที่คุณส่งข้อความไปหาได้" + _chatAllowedUsers: + everyone: "ใครก็ได้หมด" + followers: "เฉพาะผู้ติดตามเท่านั้น" + following: "เฉพาะผู้ที่ตัวเองติดตามเท่านั้น" + mutual: "เฉพาะผู้ใช้ที่ติดตามซึ่งกันและกันทั้งสองฝ่ายเท่านั้น" + none: "ไม่อนุญาตให้ใครเลย" +_emojiPalette: + palettes: "จานสี" + enableSyncBetweenDevicesForPalettes: "เปิดใช้งานการซิงค์จานสีระหว่างอุปกรณ์" + paletteForMain: "จานสีหลักที่ใช้" + paletteForReaction: "จานสีที่ใช้ในการรีแอคชั่น" _settings: + driveBanner: "สามารถจัดการและตั้งค่าไดรฟ์ ตรวจสอบการใช้งาน และตั้งค่าการอัปโหลดไฟล์ได้" + pluginBanner: "สามารถขยายความสามารถของไคลเอนต์ด้วยปลั๊กอินได้ ติดตั้ง ตั้งค่า และจัดการปลั๊กอินแต่ละตัวได้" + notificationsBanner: "สามารถตั้งค่าประเภทและขอบเขตของการแจ้งเตือนที่รับจากเซิร์ฟเวอร์ รวมถึงการแจ้งเตือนแบบพุช" + api: "API" webhook: "Webhook" + serviceConnection: "การเชื่อมต่อกับบริการ" + serviceConnectionBanner: "สามารถจัดการและตั้งค่าโทเค็นการเข้าถึงและ Webhook เพื่อเชื่อมต่อกับแอปหรือบริการภายนอกได้" + accountData: "ข้อมูลบัญชี" + accountDataBanner: "สามารถจัดการข้อมูลบัญชีได้โดยส่งออกหรือนำเข้าไฟล์เก็บถาวร" + muteAndBlockBanner: "สามารถตั้งค่าการซ่อนเนื้อหา และจำกัดการกระทำจากผู้ใช้เฉพาะรายได้" + accessibilityBanner: "สามารถปรับแต่งรูปลักษณ์และพฤติกรรมของไคลเอนต์เพื่อให้เหมาะกับการใช้งานของตนเองมากขึ้น" + privacyBanner: "สามารถตั้งค่าความเป็นส่วนตัวของบัญชี เช่น ขอบเขตการเผยแพร่เนื้อหา ความสามารถในการค้นหา และการอนุมัติผู้ติดตาม" + securityBanner: "สามารถตั้งค่าความปลอดภัยของบัญชี เช่น รหัสผ่าน วิธีการเข้าสู่ระบบ แอปยืนยันตัวตน Passkey เป็นต้น" + preferencesBanner: "คุณสามารถกำหนดค่าพฤติกรรมโดยรวมของไคลเอนต์ได้ตามความต้องการของคุณ" + appearanceBanner: "สามารถตั้งค่ารูปลักษณ์และวิธีการแสดงผลของไคลเอนต์ตามความชอบได้" + soundsBanner: "สามารถตั้งค่าเสียงที่จะเล่นบนไคลเอนต์ได้" + timelineAndNote: "ไทม์ไลน์และโน้ต" + makeEveryTextElementsSelectable: "อนุญาตให้เลือกข้อความทั้งหมดได้" + makeEveryTextElementsSelectable_description: "หากเปิดใช้งาน อาจทำให้ความสะดวกในการใช้งานลดลงในบางสถานการณ์" + useStickyIcons: "ทำให้ไอคอนเคลื่อนตามการเลื่อน" + enableHighQualityImagePlaceholders: "แสดงภาพตัวแทนคุณภาพสูง" + uiAnimations: "ภาพเคลื่อนไหวของ UI" + showNavbarSubButtons: "แสดงปุ่มรองบนแถบนำทาง" + ifOn: "เมื่อเปิดใช้งาน" + ifOff: "เมื่อปิดใช้งาน" + enableSyncThemesBetweenDevices: "ซิงค์ธีมที่ติดตั้งระหว่างอุปกรณ์" + enablePullToRefresh: "ดึงเพื่ออัปเดต" + enablePullToRefresh_description: "สำหรับเมาส์ ให้กดปุ่มล้อกลางค้างไว้แล้วลาก" + realtimeMode_description: "เชื่อมต่อกับเซิร์ฟเวอร์และอัปเดตเนื้อหาแบบเรียลไทม์ อาจทำให้ใช้ปริมาณข้อมูลและแบตเตอรี่มากขึ้นได้" + contentsUpdateFrequency: "ความถี่ในการดึงข้อมูลเนื้อหา" + contentsUpdateFrequency_description: "ยิ่งตั้งค่าสูง เนื้อหาจะอัปเดตแบบเรียลไทม์มากขึ้น แต่ประสิทธิภาพอาจลดลง และการใช้ข้อมูลกับแบตเตอรี่จะเพิ่มมากขึ้น" + contentsUpdateFrequency_description2: "เมื่อโหมดเรียลไทม์เปิดอยู่ เนื้อหาจะอัปเดตแบบเรียลไทม์โดยไม่ขึ้นกับการตั้งค่านี้" + showUrlPreview: "แสดงตัวอย่าง URL" + showAvailableReactionsFirstInNote: "แสดงรีแอคชั่นที่ใช้ได้ไว้หน้าสุด" + showPageTabBarBottom: "แสดงแท็บบาร์ของเพจที่ด้านล่าง" + _chat: + showSenderName: "แสดงชื่อผู้ส่ง" + sendOnEnter: "กด Enter เพื่อส่ง" +_preferencesProfile: + profileName: "ชื่อโปรไฟล์" + profileNameDescription: "กรุณาตั้งชื่อเพื่อระบุอุปกรณ์นี้" + profileNameDescription2: "เช่น: “คอมเครื่องหลัก”, “มือถือ” ฯลฯ" + manageProfiles: "จัดการโปรไฟล์" +_preferencesBackup: + autoBackup: "สำรองโดยอัตโนมัติ" + restoreFromBackup: "คืนค่าจากข้อมูลสำรอง" + noBackupsFoundTitle: "ไม่พบข้อมูลสำรอง" + noBackupsFoundDescription: "ไม่พบข้อมูลสำรองที่สร้างโดยอัตโนมัติ แต่หากมีข้อมูลสำรองที่บันทึกด้วยตนเอง สามารถนำเข้ามาเพื่อกู้คืนได้" + selectBackupToRestore: "กรุณาเลือกข้อมูลสำรองที่ต้องการกู้คืน" + youNeedToNameYourProfileToEnableAutoBackup: "จำเป็นต้องตั้งชื่อโปรไฟล์ก่อนจึงจะเปิดใช้งานการสำรองข้อมูลอัตโนมัติได้" + autoPreferencesBackupIsNotEnabledForThisDevice: "ยังไม่ได้เปิดใช้งานการสำรองข้อมูลอัตโนมัติบนอุปกรณ์นี้" + backupFound: "พบข้อมูลสำรองของการตั้งค่าแล้ว" +_accountSettings: + requireSigninToViewContents: "ต้องเข้าสู่ระบบเพื่อดูเนื้อหา" + requireSigninToViewContentsDescription1: "กำหนดให้ต้องเข้าสู่ระบบก่อนจึงจะสามารถดูโน้ตหรือเนื้อหาทั้งหมดที่สร้างไว้ได้ ซึ่งช่วยป้องกันไม่ให้ข้อมูลถูกเก็บโดยบอตหรือ Crawler (โปรแกรมรวบรวมข้อมูล)" + requireSigninToViewContentsDescription2: "จะไม่สามารถแสดงผลจากเซิร์ฟเวอร์ที่ไม่รองรับการแสดงตัวอย่าง URL (OGP), การฝังในหน้าเว็บ, หรือการอ้างอิงโน้ตได้" + requireSigninToViewContentsDescription3: "เนื้อหาที่ถูกรวมผ่านสหพันธ์จากเซิร์ฟเวอร์ระยะไกลอาจไม่อยู่ภายใต้ข้อจำกัดเหล่านี้" + makeNotesFollowersOnlyBefore: "แสดงโน้ตเก่าเฉพาะกับผู้ติดตามเท่านั้น" + makeNotesFollowersOnlyBeforeDescription: "ขณะที่เปิดฟังก์ชันนี้ โน้ตที่เก่ากว่าหรือเลยเวลาที่กำหนดจะแสดงเฉพาะกับผู้ติดตามเท่านั้น หากปิดใช้งาน สถานะการเปิดเผยจะกลับไปเป็นแบบเดิม" + makeNotesHiddenBefore: "ทำให้โน้ตเก่าทั้งหมดเป็นแบบส่วนตัว" + makeNotesHiddenBeforeDescription: "ขณะที่เปิดฟังก์ชันนี้ โน้ตที่เก่ากว่าหรือเลยเวลาที่กำหนดจะแสดงเฉพาะกับตนเอง (กลายเป็นแบบส่วนตัว) หากปิดใช้งาน สถานะการเปิดเผยจะกลับไปเป็นแบบเดิม" + mayNotEffectForFederatedNotes: "โน้ตที่ถูกรวมผ่านสหพันธ์จากเซิร์ฟเวอร์ระยะไกลอาจไม่ได้รับผลจากการตั้งค่านี้" + mayNotEffectSomeSituations: "ข้อจำกัดเหล่านี้เป็นเพียงการกรองเบื้องต้น ในบางกรณี เช่น การดูจากเซิร์ฟเวอร์อื่นหรือในระหว่างการตรวจสอบโดยผู้ดูแล อาจไม่สามารถใช้งานได้" + notesHavePassedSpecifiedPeriod: "โน้ตที่เลยเวลาที่กำหนดไว้แล้ว" + notesOlderThanSpecifiedDateAndTime: "โน้ตก่อนเวลาที่กำหนดไว้" _abuseUserReport: forward: "ส่ง​ต่อ" forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวอร์ระยะไกลโดยใช้บัญชีระบบที่ไม่ระบุตัวตน" resolve: "แก้ไข" accept: "ยอมรับ" reject: "ปฏิเสธ" - resolveTutorial: "ถ้าหากรายงานนี้มีเนื้อหาถูกต้อง ให้เลือก \"ยอมรับ\" เพื่อปิดเคสกรณีนี้โดยถือว่าได้รับการแก้ไขแล้ว\nถ้าหากเนื้อหาในรายงานนี้นั้นไม่ถูกต้อง ให้เลือก \"ปฏิเสธ\" เพื่อปิดเคสกรณีนี้โดยถือว่าไม่ได้รับการแก้ไข" + resolveTutorial: "ให้เลือก “ยอมรับ” หากรายงานนี้มีเนื้อหาชอบธรรม เพื่อทำเครื่องหมายว่ากรณีนี้ได้รับการแก้ไขในทางบวก\nให้เลือก “ปฏิเสธ” หากรายงานนี้มีเนื้อหาไม่สมเหตุผล เพื่อทำเครื่องหมายว่ากรณีนี้ได้รับการแก้ไขในทางลบ" _delivery: status: "สถานะการจัดส่ง" stop: "ระงับการส่ง" @@ -1314,6 +1549,7 @@ _delivery: manuallySuspended: "หยุดชั่วคราวด้วยตนเอง" goneSuspended: "เซิร์ฟเวอร์ถูกระงับเนื่องจากมีการลบเซิร์ฟเวอร์นี้" autoSuspendedForNotResponding: "เซิร์ฟเวอร์ถูกระงับเนื่องจากไม่ตอบสนอง" + softwareSuspended: "หยุดให้บริการ เนื่องจากเป็นซอฟต์แวร์ที่ถูกระงับการเผยแพร่" _bubbleGame: howToPlay: "วิธีเล่น" hold: "ถือไว้" @@ -1428,7 +1664,7 @@ _timelineDescription: _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" _serverSettings: - iconUrl: "URL ไอคอน" + iconUrl: "URL ของไอคอน" appIconDescription: "ระบุไอคอนที่จะใช้เมื่อ {host} แสดงเป็นแอป" appIconUsageExample: "ตัวอย่างเช่น เมื่อถูกเพิ่มเป็น PWA หรือบุ๊กมาร์กบนหน้าจอหลักในสมาร์ทโฟน" appIconStyleRecommendation: "เนื่องจากอาจถูกครอบตัดเป็นสี่เหลี่ยมหรือวงกลม จึงแนะนำให้ใช้ภาพที่เผื่อพื้นที่รอบๆ ตัวโลโก้ไอคอนไว้" @@ -1440,9 +1676,37 @@ _serverSettings: fanoutTimelineDbFallback: "ฟอลแบ๊กกลับฐานข้อมูล" fanoutTimelineDbFallbackDescription: "เมื่อเปิดใช้งาน หากไม่ได้แคชไทม์ไลน์ ไทม์ไลน์จะฟอลแบ๊กไปยังฐานข้อมูลสำหรับการ query เพิ่มเติม การปิดใช้งานจะช่วยลดภาระของเซิร์ฟเวอร์ด้วยการกำจัดกระบวนฟอลแบ๊ก แต่มันก็จะจำกัดช่วงเวลาไทม์ไลน์ที่สามารถดึงข้อมูลได้" reactionsBufferingDescription: "เมื่อเปิดใช้งานฟังก์ชันนี้ก็จะช่วยลด latency ในการสร้างปฏิกิริยา แต่อาจจะส่งผลให้ memory footprint ของ Redis เพิ่มขึ้นนะ" + remoteNotesCleaning: "การล้างข้อมูลโพสต์จากระยะไกลโดยอัตโนมัติ" + remoteNotesCleaning_description: "เมื่อเปิดใช้งาน จะทำการล้างโพสต์จากระยะไกลเก่าที่ไม่ถูกอ้างอิง เป็นระยะ เพื่อลดการขยายตัวของฐานข้อมูล" + remoteNotesCleaningMaxProcessingDuration: "ระยะเวลาสูงสุดของการประมวลผลการล้างข้อมูล" + remoteNotesCleaningExpiryDaysForEachNotes: "จำนวนวันที่ต้องเก็บโน้ตไว้อย่างน้อย" inquiryUrl: "URL สำหรับการติดต่อสอบถาม" inquiryUrlDescription: "ระบุ URL ของหน้าเว็บที่มีแบบฟอร์มสำหรับติดต่อผู้ดูแลเซิร์ฟเวอร์ หรือข้อมูลการติดต่อของผู้ดูแลเซิร์ฟเวอร์" - thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ถ้าหากไม่มีการตรวจสอบจากผู้ดูแลระบบหรือไม่มีความเคลื่อนไหวมาเป็นระยะเวลาหนึ่ง ระบบจะทำการปิดใช้งานฟังก์ชันนี้โดยอัตโนมัติ เพื่อลดความเสี่ยงในการถูกโจมตีด้วยสแปมและอื่นๆ" + openRegistration: "เปิดให้สร้างบัญชีได้" + openRegistrationWarning: "การเปิดให้ลงทะเบียนมีความเสี่ยง แนะนำให้เปิดใช้งานเฉพาะในกรณีที่สามารถตรวจสอบเซิร์ฟเวอร์อย่างสม่ำเสมอและมีระบบรับมือกับปัญหาได้ทันท่วงที" + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "หากไม่พบกิจกรรมของผู้ควบคุมในช่วงระยะเวลาหนึ่ง การตั้งค่านี้จะถูกปิดโดยอัตโนมัติเพื่อป้องกันสแปม" + deliverSuspendedSoftware: "ซอฟต์แวร์ที่หยุดการเผยแพร่" + deliverSuspendedSoftwareDescription: "เนื่องจากเหตุผลด้านช่องโหว่ เป็นต้น สามารถหยุดการแจกจ่ายโดยระบุชื่อซอฟต์แวร์ของเซิร์ฟเวอร์และช่วงของเวอร์ชันได้ ข้อมูลเวอร์ชันนี้เป็นข้อมูลที่เซิร์ฟเวอร์ให้มา จึงไม่สามารถรับประกันความน่าเชื่อถือได้ สามารถใช้การระบุช่วงเวอร์ชันแบบ semver ได้ แต่ถ้าระบุเป็น >= 2024.3.1 จะไม่รวมเวอร์ชันแบบกำหนดเอง เช่น 2024.3.1-custom.0 จึงแนะนำให้ระบุเป็น >= 2024.3.1-0 ซึ่งเป็นการระบุแบบ prerelease" + singleUserMode: "โหมดผู้ใช้คนเดียว" + singleUserMode_description: "หากมีเพียงตัวเองคนเดียวที่ใช้เซิร์ฟเวอร์นี้ การเปิดใช้งานโหมดนี้จะช่วยปรับการทำงานให้เหมาะสมที่สุด" + signToActivityPubGet: "ลงนามในคำขอ GET" + signToActivityPubGet_description: "โดยปกติควรเปิดใช้งาน แต่หากพบปัญหาเกี่ยวกับการสื่อสารในสหพันธ์ การปิดใช้งานอาจช่วยแก้ไขได้ แต่ในบางกรณี เซิร์ฟเวอร์อาจไม่สามารถสื่อสารได้เลยหากปิดใช้งานนี้" + proxyRemoteFiles: "พร็อกซีไฟล์ระยะไกล" + proxyRemoteFiles_description: "เมื่อเปิดใช้งาน จะทำหน้าที่เป็นพร็อกซีสำหรับไฟล์จากระยะไกล ช่วยในการสร้างภาพขนาดย่อและปกป้องความเป็นส่วนตัวของผู้ใช้" + allowExternalApRedirect: "อนุญาตการเปลี่ยนเส้นทางการสืบค้นผ่าน ActivityPub" + allowExternalApRedirect_description: "เมื่อเปิดใช้งาน จะอนุญาตให้เซิร์ฟเวอร์อื่นสืบค้นเนื้อหาของบุคคลที่สามผ่านเซิร์ฟเวอร์นี้ได้ แต่มีความเสี่ยงที่อาจเกิดการปลอมแปลงเนื้อหา" + userGeneratedContentsVisibilityForVisitor: "ขอบเขตการเปิดเผยเนื้อหาที่ผู้ใช้สร้างต่อบุคคลที่ไม่ได้เข้าร่วม (แขก)" + userGeneratedContentsVisibilityForVisitor_description: "ช่วยป้องกันปัญหาที่อาจเกิดขึ้นจากเนื้อหาระยะไกลที่ไม่เหมาะสม ซึ่งอาจถูกเผยแพร่ออกสู่อินเทอร์เน็ตโดยไม่ตั้งใจผ่านเซิร์ฟเวอร์ของตนเอง โดยเฉพาะในกรณีที่การดูแลควบคุมไม่ทั่วถึง" + userGeneratedContentsVisibilityForVisitor_description2: "การเปิดเผยเนื้อหาทั้งหมดในเซิร์ฟเวอร์รวมทั้งเนื้อหาที่รับมาจากระยะไกลสู่สาธารณะบนอินเทอร์เน็ตโดยไม่มีข้อจำกัดใดๆ มีความเสี่ยงโดยเฉพาะอย่างยิ่งสำหรับผู้ชมที่ไม่เข้าใจลักษณะของระบบแบบกระจาย อาจทำให้เกิดความเข้าใจผิดคิดว่าเนื้อหาที่มาจากระยะไกลนั้นเป็นเนื้อหาที่สร้างขึ้นภายในเซิร์ฟเวอร์นี้ จึงควรใช้ความระมัดระวังอย่างมาก" + restartServerSetupWizardConfirm_title: "ต้องการเริ่มวิซาร์ดการตั้งค่าเซิร์ฟเวอร์ใหม่หรือไม่?" + restartServerSetupWizardConfirm_text: "การตั้งค่าบางส่วนในปัจจุบันจะถูกรีเซ็ต" + entrancePageStyle: "สไตล์ของหน้าเพจทางเข้า" + showTimelineForVisitor: "แสดงไทม์ไลน์" + showActivitiesForVisitor: "แสดงกิจกรรม" + _userGeneratedContentsVisibilityForVisitor: + all: "ทั้งหมดสาธารณะ" + localOnly: "เผยแพร่เป็นสาธารณะเฉพาะเนื้อหาท้องถิ่น เนื้อหาระยะไกลให้เป็นส่วนตัว" + none: "ทั้งหมดส่วนตัว" _accountMigration: moveFrom: "ย้ายจากบัญชีอื่นมาที่บัญชีนี้" moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น" @@ -1732,13 +1996,15 @@ _role: baseRole: "แม่แบบบทบาท" useBaseValue: "ใช้ตามแม่แบบบทบาท" chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด" - iconUrl: "URL ไอคอน" + iconUrl: "URL ของไอคอน" asBadge: "แสดงเป็นตรา" - descriptionOfAsBadge: "เมื่อเปิดใช้งาน ไอคอนบทบาทจะปรากฏถัดจากชื่อผู้ใช้" + descriptionOfAsBadge: "หากเปิดใช้งาน จะมีไอคอนของบทบาท แสดงถัดจากชื่อผู้ใช้" isExplorable: "ค้นหาผู้ใช้ได้ง่ายขึ้นโดยดูจากบทบาท" descriptionOfIsExplorable: "เมื่อเปิดใช้งาน ไทมไลน์บทบาทนี้และสมาชิกที่มีบทบาทนี้จะเปิดเผยเป็นสาธารณะ" displayOrder: "ลำดับการแสดงผล" descriptionOfDisplayOrder: "เลขที่สูงกว่าจะแสดงบน UI ก่อน" + preserveAssignmentOnMoveAccount: "โอนสถานะการมอบหมายไปยังบัญชีที่ย้ายไป" + preserveAssignmentOnMoveAccount_description: "เมื่อเปิดใช้งาน บัญชีที่ได้รับบทบาทนี้เมื่อถูกย้ายไปบัญชีใหม่ บทบาทนี้จะถูกถ่ายทอดไปยังบัญชีปลายทางด้วย" canEditMembersByModerator: "อนุญาตให้ผู้ควบคุมแก้ไขสมาชิก" descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ นอกเหนือจากผู้ควบคุมและผู้ดูแลระบบแล้ว จะสามารถเพิ่มถอนบทบาทนี้แก่ผู้ใช้ได้ แต่เมื่อปิดใช้ จะมีเฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถดำเนินการได้" priority: "ลำดับความสำคัญ" @@ -1758,8 +2024,9 @@ _role: canManageCustomEmojis: "จัดการเอโมจิที่กำหนดเอง" canManageAvatarDecorations: "จัดการตกแต่งอวตาร" driveCapacity: "ความจุของไดรฟ์" + maxFileSize: "ขนาดไฟล์สูงสุดที่สามารถอัปโหลดได้" alwaysMarkNsfw: "ทำเครื่องหมายไฟล์ว่าเป็น NSFW เสมอ" - canUpdateBioMedia: "อนุญาตให้ปรับปรุงไอคอนและแบนเนอร์" + canUpdateBioMedia: "อนุญาตให้เปลี่ยนไอคอนประจำตัวและแบนเนอร์" pinMax: "จํานวนสูงสุดของโน้ตที่ปักหมุดไว้" antennaMax: "จำนวนสูงสุดของเสาอากาศ" wordMuteMax: "จำนวนอักขระสูงสุดที่อนุญาตในการปิดเสียงคำ" @@ -1772,13 +2039,21 @@ _role: descriptionOfRateLimitFactor: "ยิ่งตัวเลขน้อยก็ยิ่งจำกัดน้อย ยิ่งมากก็ยิ่งเข้มงวดมากขึ้น" canHideAds: "ซ่อนโฆษณา" canSearchNotes: "การใช้การค้นหาโน้ต" + canSearchUsers: "ค้นหาผู้ใช้" canUseTranslator: "การใช้งานแปล" - avatarDecorationLimit: "จำนวนการตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" + avatarDecorationLimit: "จำนวนของตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" canImportAntennas: "อนุญาตให้นำเข้าเสาอากาศ" canImportBlocking: "อนุญาตให้นำเข้าการบล็อก" canImportFollowing: "อนุญาตให้นำเข้ารายการต่อไปนี้" - canImportMuting: "อนุญาตให้นำเข้าการปิดกั้น" + canImportMuting: "อนุญาตให้นำเข้าการปิดเสียง" canImportUserLists: "อนุญาตให้นำเข้ารายการ" + chatAvailability: "อนุญาตให้แชต" + uploadableFileTypes: "ประเภทไฟล์ที่สามารถอัปโหลดได้" + uploadableFileTypes_caption: "สามารถระบุ MIME type ได้ โดยใช้การขึ้นบรรทัดใหม่เพื่อแยกหลายรายการ และสามารถใช้ดอกจัน (*) เพื่อระบุแบบไวลด์การ์ดได้ (เช่น: image/*)" + uploadableFileTypes_caption2: "ไฟล์บางประเภทอาจไม่สามารถระบุชนิดได้ หากต้องการอนุญาตไฟล์ลักษณะนั้น กรุณาเพิ่ม {x} ลงในรายการที่อนุญาต" + noteDraftLimit: "จำนวนโน้ตฉบับร่างที่สามารถสร้างได้บนฝั่งเซิร์ฟเวอร์" + scheduledNoteLimit: "จำนวนโพสต์กำหนดเวลาที่สร้างพร้อมกันได้" + watermarkAvailable: "มีฟังก์ชั่นลายน้ำให้เลือกใช้" _condition: roleAssignedTo: "มอบหมายให้มีบทบาทแบบทำมือ" isLocal: "ผู้ใช้ท้องถิ่น" @@ -1938,10 +2213,12 @@ _theme: install: "ติดตั้งธีม" manage: "จัดการธีม" code: "โค้ดธีม" - description: "รายละเอียด" + copyThemeCode: "คัดลอกรหัสธีม" + description: "คำอธิบาย" installed: "{name} ได้รับการติดตั้ง" installedThemes: "ธีมที่ติดตั้ง" builtinThemes: "ธีมในตัว" + instanceTheme: "ธีมของเซิร์ฟเวอร์" alreadyInstalled: "ธีมนี้ได้รับการติดตั้งแล้ว" invalid: "รูปแบบของธีมนี้ไม่ถูกต้องนะ" make: "ทำธีม" @@ -1969,7 +2246,7 @@ _theme: fg: "ข้อความ" focus: "โฟกัส" indicator: "ตัวบ่งชี้" - panel: "แผงควบคุม" + panel: "แผง" shadow: "เงา" header: "ส่วนหัว" navBg: "พื้นหลังแถบด้านข้าง" @@ -1979,7 +2256,7 @@ _theme: link: "ลิงก์" hashtag: "แฮชแท็ก" mention: "กล่าวถึง" - mentionMe: "ได้กล่าวถึง (ฉัน)" + mentionMe: "ได้กล่าวถึงคุณ" renote: "รีโน้ต" modalBg: "พื้นหลังโมดอล" divider: "ตัวแบ่ง" @@ -1995,7 +2272,6 @@ _theme: buttonBg: "ปุ่มพื้นหลัง" buttonHoverBg: "ปุ่มพื้นหลัง (โฮเวอร์)" inputBorder: "เส้นขอบของช่องป้อนข้อมูล" - driveFolderBg: "พื้นหลังโฟลเดอร์ไดรฟ์" badge: "ตรา" messageBg: "พื้นหลังแชท" fgHighlighted: "ข้อความที่ไฮไลต์" @@ -2004,6 +2280,7 @@ _sfx: noteMy: "โน้ตของตัวเอง" notification: "การเเจ้งเตือน" reaction: "เมื่อเลือกรีแอคชั่น" + chatMessage: "ข้อความของแชต" _soundSettings: driveFile: "ใช้เสียงจากไดรฟ์" driveFileWarn: "เลือกไฟล์ในไดรฟ์ของคุณ" @@ -2036,6 +2313,7 @@ _time: minute: "นาที" hour: "ชั่วโมง" day: "วัน" + month: "เดือน" _2fa: alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว" registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์" @@ -2046,15 +2324,15 @@ _2fa: step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า" setupCompleted: "ตั้งค่าสำเร็จแล้ว" step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว" - securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ" - registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" - securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ" - registerSecurityKey: "ลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" + securityKeyNotSupported: "เว็บเบราว์เซอร์ที่ใช้งานอยู่ไม่รองรับ Security Key" + registerTOTPBeforeKey: "ก่อนลงทะเบียน Security Key หรือ Passkey กรุณาตั้งค่าแอปยืนยันตัวตนก่อน" + securityKeyInfo: "ลงทะเบียนกุญแจที่มาจาก WebAuthn เช่น Security Key แบบฮาร์ดแวร์ที่รองรับ FIDO2 การยืนยันตัวตนด้วยชีวมิติหรือ PIN บนอุปกรณ์ และ Passkey" + registerSecurityKey: "ลงทะเบียน Security Key หรือ Passkey" securityKeyName: "ป้อนชื่อคีย์" - tapSecurityKey: "กรุณาทำตามเบราว์เซอร์ของคุณเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" - removeKey: "ลบคีย์ความปลอดภัยออก" + tapSecurityKey: "กรุณาทำตามคำแนะนำของเบราว์เซอร์เพื่อลงทะเบียน Security Key หรือ Passkey" + removeKey: "ลบ Security Key ออก" removeKeyConfirm: "ลบข้อมูลสำรอง {name} มั้ย?" - whyTOTPOnlyRenew: "ไม่สามารถลบแอปตัวรับรองความถูกต้องได้ตราบใดที่มีการลงทะเบียนคีย์ความปลอดภัยไว้แล้ว" + whyTOTPOnlyRenew: "ไม่สามารถลบแอปตัวรับรองความถูกต้องได้ตราบใดที่ยังมีการลงทะเบียน Security Key อยู่" renewTOTP: "ตั้งค่าแอปยืนยันตัวตน" renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ" renewTOTPOk: "ตั้งค่าคอนฟิกใหม่" @@ -2151,6 +2429,7 @@ _permissions: "read:federation": "รับข้อมูลเกี่ยวกับสหพันธ์" "write:report-abuse": "รายงานการละเมิด" "write:chat": "เขียนหรือลบข้อความแชท" + "read:chat": "อ่านแชต" _auth: shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน" shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?" @@ -2159,8 +2438,11 @@ _auth: permissionAsk: "แอปพลิเคชันนี้ขอสิทธิ์ดังต่อไปนี้" pleaseGoBack: "กรุณากลับไปที่แอปพลิเคชัน" callback: "กำลังกลับไปที่แอปพลิเคชัน" + accepted: "การเข้าถึงได้รับอนุญาต" denied: "ปฏิเสธการเข้าใช้" + scopeUser: "กำลังดำเนินการในฐานะผู้ใช้ต่อไปนี้" pleaseLogin: "กรุณาเข้าสู่ระบบเพื่ออนุมัติแอปพลิเคชัน" + byClickingYouWillBeRedirectedToThisUrl: "หากอนุญาตการเข้าถึง ระบบจะเปลี่ยนเส้นทางไปยัง URL ด้านล่างโดยอัตโนมัติ" _antennaSources: all: "โน้ตทั้งหมด" homeTimeline: "โน้ตจากผู้ใช้ที่ติดตาม" @@ -2206,6 +2488,16 @@ _widgets: chooseList: "เลือกรายชื่อ" clicker: "คลิกเกอร์" birthdayFollowings: "วันเกิดผู้ใช้ในวันนี้" + chat: "แชตเลย" +_widgetOptions: + showHeader: "แสดงส่วนหัว" + height: "ความสูง" + _button: + colored: "สี" + _clock: + size: "ขนาด" + _birthdayFollowings: + period: "ระยะเวลา" _cw: hide: "ซ่อน" show: "โหลดเพิ่มเติม" @@ -2245,9 +2537,14 @@ _visibility: disableFederation: "การปิดใช้งานสหพันธ์" disableFederationDescription: "อย่าส่งข้อมูลไปยังเซิร์ฟเวอร์อื่น" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการละทิ้งและปิดฟอร์มหรือไม่?" + uploaderTip: "ไฟล์ยังไม่ได้อัปโหลด สามารถตั้งค่าต่างๆ ได้จากเมนูของไฟล์ เช่น การเปลี่ยนชื่อ การครอปรูป การใส่ลายน้ำ และการบีบอัด ไฟล์จะถูกอัปโหลดโดยอัตโนมัติเมื่อโพสต์โน้ต" replyPlaceholder: "ตอบกลับโน้ตนี้..." quotePlaceholder: "อ้างโน้ตนี้..." channelPlaceholder: "โพสต์ลงช่อง..." + _howToUse: + visibility_title: "การมองเห็น" + menu_title: "เมนู" _placeholders: a: "ตอนนี้เป็นยังไงบ้าง?" b: "มีอะไรเกิดขึ้นหรือเปล่า?" @@ -2265,7 +2562,7 @@ _profile: metadataDescription: "ใช้สิ่งเหล่านี้ คุณสามารถแสดงฟิลด์ข้อมูลเพิ่มเติมในโปรไฟล์ของคุณ" metadataLabel: "ป้ายชื่อ" metadataContent: "เนื้อหา" - changeAvatar: "เปลี่ยนอวาตาร์" + changeAvatar: "เปลี่ยนไอคอนประจำตัว" changeBanner: "เปลี่ยนแบนเนอร์" verifiedLinkDescription: "หากป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณ ไอคอนการยืนยันความเป็นเจ้าของจะแสดงถัดจากฟิลด์นั้น ๆ" avatarDecorationMax: "คุณสามารถเพิ่มการตกแต่งได้สูงสุด {max}" @@ -2278,7 +2575,7 @@ _exportOrImport: clips: "คลิป" followingList: "กำลังติดตาม" muteList: "ปิดเสียง" - blockingList: "บล็อค" + blockingList: "บล็อก" userLists: "รายชื่อ" excludeMutingUsers: "ยกเว้นผู้ใช้ที่ปิดเสียง" excludeInactiveUsers: "ยกเว้นผู้ใช้ที่ไม่ได้ใช้งาน" @@ -2348,7 +2645,7 @@ _pages: featured: "เป็นที่นิยม" inspector: "ตัวตรวจสอบ" contents: "เนื้อหา" - content: "บล็อคหน้าเพจ" + content: "บล็อกหน้าเพจ" variables: "ตัวแปร" title: "หัวข้อ" url: "URL ของหน้า" @@ -2360,7 +2657,7 @@ _pages: fontSansSerif: "Sans Serif" eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่อ" eyeCatchingImageRemove: "ลบภาพขนาดย่อ" - chooseBlock: "เพิ่มบล็อค" + chooseBlock: "เพิ่มบล็อก" enterSectionTitle: "ป้อนชื่อหัวข้อ" selectType: "เลือกชนิด" contentBlocks: "เนื้อหา" @@ -2393,9 +2690,12 @@ _notification: youReceivedFollowRequest: "ได้รับคำขอติดตาม" yourFollowRequestAccepted: "คำขอติดตามได้รับการอนุมัติแล้ว" pollEnded: "ผลโพลออกมาแล้ว" + scheduledNotePosted: "โน้ตที่กำหนดเวลาไว้ได้ถูกโพสต์แล้ว" + scheduledNotePostFailed: "ล้มเหลวในการโพสต์โน้ตที่กำหนดเวลาไว้" newNote: "โพสต์ใหม่" unreadAntennaNote: "เสาอากาศ {name}" roleAssigned: "ได้รับบทบาท" + chatRoomInvitationReceived: "ได้รับคำเชิญเข้าร่วมห้องแชต" emptyPushNotificationMessage: "อัปเดตการแจ้งเตือนแบบพุชแล้ว" achievementEarned: "รับความสำเร็จ" testNotification: "ทดสอบการแจ้งเตือน" @@ -2408,7 +2708,9 @@ _notification: followedBySomeUsers: "มีผู้ติดตาม {n} ราย" flushNotification: "ล้างประวัติการแจ้งเตือน" exportOfXCompleted: "การดำเนินการส่งออก {x} ได้เสร็จสิ้นลงแล้ว" - login: "มีคนล็อกอิน" + login: "มีการเข้าสู่ระบบ" + createToken: "สร้างโทเค็นการเข้าถึงแล้ว" + createTokenDescription: "หากไม่ทราบสาเหตุของคำเชิญ กรุณาลบโทเค็นการเข้าถึงผ่านทาง “{text}”" _types: all: "ทั้งหมด" note: "โน้ตใหม่" @@ -2422,9 +2724,11 @@ _notification: receiveFollowRequest: "ได้รับคำร้องขอติดตาม" followRequestAccepted: "อนุมัติให้ติดตามแล้ว" roleAssigned: "ให้บทบาท" + chatRoomInvitationReceived: "เชิญเข้าห้องแชต" achievementEarned: "ปลดล็อกความสำเร็จแล้ว" exportCompleted: "กระบวนการส่งออกข้อมูลได้เสร็จสิ้นสมบูรณ์แล้ว" login: "เข้าสู่ระบบ" + createToken: "สร้างโทเค็นการเข้าถึง" test: "ทดสอบระบบแจ้งเตือน" app: "การแจ้งเตือนจากแอปที่มีลิงก์" _actions: @@ -2434,6 +2738,9 @@ _notification: _deck: alwaysShowMainColumn: "แสดงคอลัมน์หลักเสมอ" columnAlign: "จัดแนวคอลัมน์" + columnGap: "ช่องห่างระว่างคอลัมน์" + deckMenuPosition: "ตำแหน่งเมนูเด็ค" + navbarPosition: "ตำแหน่งของแถบนำทาง" addColumn: "เพิ่มคอลัมน์" newNoteNotificationSettings: "ตั้งค่าการแจ้งเตือนเมื่อมีโน้ตใหม่" configureColumn: "ตั้งค่าคอลัมน์" @@ -2452,6 +2759,7 @@ _deck: useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย " usedAsMinWidthWhenFlexible: "ความกว้างขั้นต่ำนั้นจะถูกใช้งานสำหรับสิ่งนี้เมื่อเปิดใช้งานตัวเลือก \"ปรับความกว้างอัตโนมัติ\" หากเลือกเปิดใช้งานแล้ว" flexible: "ปรับความกว้างอัตโนมัติ" + enableSyncBetweenDevicesForProfiles: "เปิดใช้งานการซิงค์ข้อมูลโปรไฟล์ระหว่างอุปกรณ์" _columns: main: "หลัก" widgets: "วิดเจ็ต" @@ -2463,6 +2771,7 @@ _deck: mentions: "กล่าวถึงคุณ" direct: "ไดเร็กต์" roleTimeline: "บทบาทไทม์ไลน์" + chat: "แชตเลย" _dialog: charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}" charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}" @@ -2491,8 +2800,8 @@ _webhookSettings: abuseReport: "เมื่อมีการรายงานจากผู้ใช้" abuseReportResolved: "เมื่อมีการจัดการกับการรายงานจากผู้ใช้" userCreated: "เมื่อผู้ใช้ถูกสร้างขึ้น" - inactiveModeratorsWarning: "เมื่อผู้ดูแลระบบไม่ได้ใช้งานมานานระยะหนึ่ง" - inactiveModeratorsInvitationOnlyChanged: "เมื่อผู้ดูแลระบบที่ไม่ได้ใช้งานมานาน และเซิร์ฟเวอร์เปลี่ยนเป็นแบบเชิญเข้าร่วมเท่านั้น" + inactiveModeratorsWarning: "เมื่อผู้ควบคุมไม่มีความเคลื่อนไหวในช่วงระยะเวลาหนึ่ง" + inactiveModeratorsInvitationOnlyChanged: "เมื่อผู้ควบคุมไม่มีความเคลื่อนไหวในช่วงระยะเวลาหนึ่ง ระบบจะเปลี่ยนเป็นแบบใช้คำเชิญโดยอัตโนมัติ" deleteConfirm: "ต้องการลบ Webhook ใช่ไหม?" testRemarks: "คลิกปุ่มทางด้านขวาของสวิตช์เพื่อส่ง Webhook ทดสอบที่มีข้อมูลจำลอง" _abuseReport: @@ -2544,10 +2853,10 @@ _moderationLogTypes: createAd: "สร้างโฆษณาแล้ว" deleteAd: "ลบโฆษณาออกแล้ว" updateAd: "อัปเดตโฆษณาแล้ว" - createAvatarDecoration: "สร้างการตกแต่งไอคอนแล้ว" - updateAvatarDecoration: "อัปเดตการตกแต่งไอคอนแล้ว" - deleteAvatarDecoration: "ลบการตกแต่งไอคอนแล้ว" - unsetUserAvatar: "ลบไอคอนผู้ใช้" + createAvatarDecoration: "สร้างของตกแต่งไอคอนแล้ว" + updateAvatarDecoration: "อัปเดตของตกแต่งไอคอนแล้ว" + deleteAvatarDecoration: "ลบของตกแต่งไอคอนแล้ว" + unsetUserAvatar: "เลิกตั้งไอคอนประจำตัวแล้ว" unsetUserBanner: "ลบแบนเนอร์ผู้ใช้" createSystemWebhook: "สร้าง SystemWebhook" updateSystemWebhook: "อัปเดต SystemWebhook" @@ -2559,6 +2868,8 @@ _moderationLogTypes: deletePage: "เพจถูกลบออกไปแล้ว" deleteFlash: "Play ถูกลบออกไปแล้ว" deleteGalleryPost: "โพสต์แกลเลอรี่ถูกลบออกแล้ว" + deleteChatRoom: "ลบห้องแชต" + updateProxyAccountDescription: "อัปเดตคำอธิบายของบัญชีพร็อกซี" _fileViewer: title: "รายละเอียดไฟล์" type: "ประเภทไฟล์" @@ -2566,6 +2877,7 @@ _fileViewer: url: "URL" uploadedAt: "วันที่เข้าร่วม" attachedNotes: "โน้ตที่แนบมาด้วย" + usage: "ใช้แล้ว" thisPageCanBeSeenFromTheAuthor: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น" _externalResourceInstaller: title: "ติดตั้งจากไซต์ภายนอก" @@ -2611,11 +2923,14 @@ _dataSaver: title: "โหลดสื่อ" description: "กันไม่ให้ภาพและวิดีโอโหลดโดยอัตโนมัติ แตะรูปภาพ/วิดีโอที่ซ่อนอยู่เพื่อโหลด" _avatar: - title: "รูปไอคอน" - description: "ระงับการเคลื่อนไหวของภาพไอคอน ภาพเคลื่อนไหวอาจมีขนาดไฟล์ใหญ่กว่าภาพปกติ ดังนั้นจึงสามารถช่วยในการลดการใช้ข้อมูล" - _urlPreview: - title: "ธัมบ์เนลแสดงตัวอย่าง URL" - description: "ธัมบ์เนลแสดงตัวอย่าง URL จะไม่โหลดโดยอัตโนมัติ" + title: "ปิดใช้งานภาพเคลื่อนไหวของไอคอนประจำตัว" + description: "ภาพเคลื่อนไหวของไอคอนประจำตัวจะหยุดทำงาน ภาพแบบเคลื่อนไหวมักมีขนาดไฟล์ใหญ่กว่าภาพปกติ จึงช่วยลดปริมาณการใช้ข้อมูลได้มากขึ้น" + _urlPreviewThumbnail: + title: "ซ่อนภาพขนาดย่อของการแสดงตัวอย่าง URL" + description: "ภาพขนาดย่อของการตัวอย่าง URL จะไม่ถูกโหลดอีกต่อไป" + _disableUrlPreview: + title: "ปิดการใช้งานแสดงตัวอย่าง URL" + description: "ปิดฟังก์ชันแสดงตัวอย่าง URL แตกต่างจากการซ่อนเพียงภาพขนาดย่อ ฟังก์ชันนี้จะช่วยลดการโหลดข้อมูลจากลิงก์ปลายทางทั้งหมด" _code: title: "ไฮไลต์โค้ด" description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้" @@ -2666,13 +2981,15 @@ _reversi: allowIrregularRules: "อนุญาตกฎที่ไม่ปรกติ (โหมดฟรีทุกอย่าง)" disallowIrregularRules: "ไม่อนุญาตกฎที่ไม่ปรกติ" showBoardLabels: "แสดงหมายเลขแถว/คอลัมน์บนกระดาน" - useAvatarAsStone: "ใช้รูปอวตารเป็นหมาก" + useAvatarAsStone: "ใช้ไอคอนประจำตัวเป็นหมาก" _offlineScreen: title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" _urlPreviewSetting: title: "การตั้งค่าการแสดงตัวอย่าง URL" enable: "เปิดใช้งานการแสดงตัวอย่าง URL" + allowRedirect: "อนุญาตการเปลี่ยนเส้นทางไปยังปลายทางของการแสดงตัวอย่าง" + allowRedirectDescription: "ตั้งค่าว่าจะติดตามลิงก์ที่เปลี่ยนเส้นทาง (redirect) เพื่อแสดงตัวอย่างหรือไม่ เมื่อมีการป้อน URL ที่มีการเปลี่ยนเส้นทาง หากปิดการใช้งาน จะช่วยประหยัดทรัพยากรของเซิร์ฟเวอร์ แต่จะไม่สามารถแสดงเนื้อหาจากปลายทางที่เปลี่ยนเส้นทางได้" timeout: "เวลาจำกัดในการโหลดตัวอย่าง URL (ms)" timeoutDescription: "หากเวลาที่ใช้ในการโหลดเกินค่านี้ จะไม่มีการสร้างการแสดงตัวอย่าง" maximumContentLength: "ค่าสูงสุดของ Content-Length (byte)" @@ -2693,6 +3010,62 @@ _contextMenu: app: "แอปพลิเคชัน" appWithShift: "แอปฟลิเคชันด้วยปุ่มยกแคร่ (Shift)" native: "UI ของเบราว์เซอร์" +_gridComponent: + _error: + requiredValue: "ค่านี้จำเป็นต้องกรอก" + columnTypeNotSupport: "การตรวจสอบค่าด้วย regex รองรับเฉพาะคอลัมน์ที่เป็น type:text" + patternNotMatch: "ค่านี้ไม่ตรงกับรูปแบบ {pattern}" + notUnique: "ค่านี้ต้องไม่ซ้ำกับค่าที่มีอยู่" +_roleSelectDialog: + notSelected: "ยังไม่มีการเลือก" +_customEmojisManager: + _gridCommon: + copySelectionRows: "คัดลอกแถวที่เลือกไว้" + copySelectionRanges: "คัดลือกที่เลือกไว้" + deleteSelectionRows: "ลบแถวที่เลือกไว้" + deleteSelectionRanges: "ล้างค่าช่วงที่เลือก" + searchSettings: "ตั้งค่าการค้นหา" + searchSettingCaption: "ตั้งค่าเงื่อนไขการค้นหาอย่างละเอียด" + searchLimit: "จำนวนรายการที่แสดง" + sortOrder: "ลำดับการเรียง" + registrationLogs: "ปูมการลงทะเบียน" + registrationLogsCaption: "จะแสดงปูมเมื่อมีการอัปเดตหรือลบเอโมจิ หากดำเนินการอัปเดต/ลบ หรือเปลี่ยนหน้า/รีโหลด หน้านี้ ปูมจะหายไป" + alertEmojisRegisterFailedDescription: "การอัปเดตหรือลบเอโมจิล้มเหลว กรุณาตรวจสอบรายละเอียดในปูมการลงทะเบียน" + _logs: + showSuccessLogSwitch: "แสดงปูมที่สำเร็จ" + failureLogNothing: "ไม่มีปูมความล้มเหลว" + logNothing: "ไม่มีปูม" + _remote: + selectionRowDetail: "รายละเอียดของแถวที่เลือก" + importSelectionRows: "นำเข้าแถวที่เลือก" + importSelectionRangesRows: "นำเข้าแถวในช่วงที่เลือก" + importEmojisButton: "นำเข้าเอโมจิที่ทำเครื่องหมายไว้" + confirmImportEmojisTitle: "นำเข้าเอโมจิ" + confirmImportEmojisDescription: "จะนำเข้าเอโมจิ {count} รายการที่ได้รับจากระยะไกล ทั้งนี้โปรดระมัดระวังเรื่องสิทธิ์การใช้งานเอโมจิ ดำเนินการหรือไม่?" + _local: + tabTitleList: "รายการเอโมจิที่ลงทะเบียนไว้แล้ว" + tabTitleRegister: "ลงทะเบียนเอโมจิ" + _list: + emojisNothing: "ยังไม่มีเอโมจิที่ลงทะเบียนไว้" + markAsDeleteTargetRows: "กำหนดแถวที่เลือกให้เป็นรายการสำหรับลบ" + markAsDeleteTargetRanges: "กำหนดช่วงแถวที่เลือกให้เป็นรายการสำหรับลบ" + alertUpdateEmojisNothingDescription: "ไม่มีการเปลี่ยนแปลงเอโมจิ" + alertDeleteEmojisNothingDescription: "ไม่มีเอโมจิที่อยู่ในรายการสำหรับลบ" + confirmMovePage: "ต้องการเปลี่ยนหน้าหรือไม่?" + confirmChangeView: "ต้องการเปลี่ยนการแสดงผลหรือไม่?" + confirmUpdateEmojisDescription: "จะอัปเดตเอโมจิ {count} รายการ ดำเนินการหรือไม่?" + confirmDeleteEmojisDescription: "จะลบเอโมจิที่ถูกทำเครื่องหมายไว้ {count} รายการ ดำเนินการหรือไม่?" + confirmResetDescription: "การเปลี่ยนแปลงทั้งหมดที่ทำมาจะถูกรีเซ็ต" + confirmMovePageDesciption: "มีการเปลี่ยนแปลงเอโมจิในหน้านี้ หากเปลี่ยนหน้าโดยไม่บันทึก การเปลี่ยนแปลงทั้งหมดจะถูกละทิ้ง" + dialogSelectRoleTitle: "ค้นหาบทบาทที่ตั้งค่าไว้ด้วยเอโมจิ" + _register: + uploadSettingTitle: "ตั้งค่าการอัปโหลด" + uploadSettingDescription: "สามารถกำหนดพฤติกรรมขณะอัปโหลดเอโมจิจากหน้าจอนี้ได้" + directoryToCategoryLabel: "ป้อนชื่อไดเรกทอรีเป็น \"category\"" + directoryToCategoryCaption: "เมื่อทำการลากและวางไดเรกทอรี ชื่อจะถูกป้อนเป็น \"category\"" + confirmRegisterEmojisDescription: "จะลงทะเบียนเอโมจิที่แสดงในรายการเป็นเอโมจิแบบกำหนดเองใหม่\nดำเนินการต่อหรือไม่? (เพื่อหลีกเลี่ยงภาระโหลดหนัก ระบบจะสามารถลงทะเบียนอีโมจิได้สูงสุด {count} รายการต่อครั้ง)" + confirmClearEmojisDescription: "ต้องการยกเลิกการแก้ไขและล้างรายการเอโมจิที่แสดงอยู่หรือไม่?" + confirmUploadEmojisDescription: "จะอัปโหลดไฟล์ {count} รายการที่ลากและวางไปยังไดรฟ์ ดำเนินการหรือไม่?" _embedCodeGen: title: "ปรับแต่งโค้ดฝัง" header: "แสดงส่วนหัว" @@ -2707,10 +3080,252 @@ _embedCodeGen: generateCode: "สร้างโค้ดสำหรับการฝัง" codeGenerated: "รหัสถูกสร้างขึ้นแล้ว" codeGeneratedDescription: "นำโค้ดที่สร้างแล้วไปวางในเว็บไซต์ของคุณเพื่อฝังเนื้อหา" +_selfXssPrevention: + warning: "คำเตือน" + title: "“ข้อความที่บอกให้วางบางอย่างในหน้าจอนี้” ทั้งหมดเป็นการหลอกลวง" + description1: "ถ้าวางบางอย่างที่นี่ อาจทำให้ผู้ไม่หวังดีเข้าควบคุมบัญชี หรือขโมยข้อมูลส่วนตัวได้" + description2: "ถ้าไม่เข้าใจอย่างชัดเจนว่าสิ่งที่กำลังจะวางคืออะไร %cให้หยุดการทำงานทันทีแล้วปิดหน้าต่างนี้" + description3: "ดูรายละเอียดเพิ่มเติมได้ที่นี่: {link}" +_followRequest: + recieved: "คำขอที่ได้รับ" + sent: "คำที่ส่งไป" _remoteLookupErrors: + _federationNotAllowed: + title: "ไม่สามารถสื่อสารกับเซิร์ฟเวอร์นี้ได้" + description: "การสื่อสารกับเซิร์ฟเวอร์นี้อาจถูกปิดใช้งาน หรือเซิร์ฟเวอร์นี้อาจจะได้บล็อกคุณ หรือคุณอาจจะได้บล็อกเซิร์ฟเวอร์นี้อยู่\nกรุณาติดต่อผู้ดูแลระบบเซิร์ฟเวอร์เพื่อสอบถามรายละเอียดเพิ่มเติม" + _uriInvalid: + title: "URI ไม่ถูกต้อง" + description: "มีปัญหาเกี่ยวกับ URI ที่ป้อน โปรดตรวจสอบว่าไม่มีอักขระที่ไม่สามารถใช้กับ URI" + _requestFailed: + title: "การร้องขอล้มเหลว" + description: "การสื่อสารกับเซิร์ฟเวอร์นี้ล้มเหลว เซิร์ฟเวอร์ปลายทางอาจล่ม หรืออาจป้อน URI ที่ไม่ถูกต้องหรือไม่มีอยู่" + _responseInvalid: + title: "ข้อมูลตอบสนองกลับไม่ถูกต้อง" + description: "สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้ได้ แต่ข้อมูลที่ได้รับไม่ถูกต้อง หากกำลังดึงข้อมูลจากเซิร์ฟเวอร์บุคคลที่สาม โปรดใช้ URI ที่สามารถดึงข้อมูลได้จากเซิร์ฟเวอร์ต้นทางโดยตรง" _noSuchObject: title: "ไม่พบหน้าที่ต้องการ" + description: "ไม่พบทรัพยากรที่ร้องขอ กรุณาตรวจสอบ URI อีกครั้ง" +_captcha: + verify: "กรุณาผ่าน CAPTCHA" + testSiteKeyMessage: "สามารถดูตัวอย่างได้โดยป้อนค่าทดสอบใน site key และ secret key\nดูรายละเอียดเพิ่มเติมได้ที่หน้าด้านล่างนี้" + _error: + _requestFailed: + title: "การร้องขอ CAPTCHA ล้มเหลว" + text: "โปรดลองใหม่ภายหลัง หรือ ตรวจสอบการตั้งค่าอีกครั้ง" + _verificationFailed: + title: "การยืนยัน CAPTCHA ล้มเหลว" + text: "กรุณาตรวจสอบอีกครั้งว่าการตั้งค่าถูกต้องหรือไม่" + _unknown: + title: "CAPTCHA เกิดข้อผิดพลาด" + text: "เกิดข้อผิดพลาดที่ไม่คาดคิด" +_bootErrors: + title: "การโหลดล้มเหลว" + serverError: "หากปัญหายังคงอยู่แม้ว่าจะรอสักครู่แล้วโหลดหน้าใหม่อีกครั้ง โปรดติดต่อผู้ดูแลระบบเซิร์ฟเวอร์พร้อมรหัสข้อผิดพลาดต่อไปนี้" + solution: "สิ่งต่อไปนี้อาจช่วยแก้ไขปัญหาได้" + solution1: "อัปเดตเบราว์เซอร์และระบบปฏิบัติการเป็นรุ่นล่าสุด" + solution2: "ปิดใช้งานตัวบล็อกโฆษณา" + solution3: "ล้างแคชเบราว์เซอร์" + solution4: "(Tor Browser) ตั้งค่า dom.webaudio.enabled เป็น true" + otherOption: "ตัวเลือกเพิ่มเติม" + otherOption1: "ลบการตั้งค่าและแคชของไคลเอนต์" + otherOption2: "เริ่มใช้งานไคลเอนต์แบบง่าย" + otherOption3: "เปิดเครื่องมือซ่อมแซม" + otherOption4: "เริ่มทำงาน Misskey ในโหมดปลอดภัย" _search: searchScopeAll: "ทั้งหมด" searchScopeLocal: "ท้องถิ่น" + searchScopeServer: "ระบุเซิร์ฟเวอร์" searchScopeUser: "ผู้ใช้เฉพาะ" + pleaseEnterServerHost: "กรุณากรอกโฮสต์ของเซิร์ฟเวอร์" + pleaseSelectUser: "กรุณาเลือกผู้ใช้" + serverHostPlaceholder: "ตัวอย่าง: misskey.example.com" +_serverSetupWizard: + installCompleted: "การติดตั้ง Misskey เสร็จสมบูรณ์แล้ว!" + firstCreateAccount: "ขั้นแรก ให้สร้างบัญชีผู้ดูแลระบบ" + accountCreated: "บัญชีผู้ดูแลระบบถูกสร้างขึ้นแล้ว!" + serverSetting: "การตั้งค่าเซิร์ฟเวอร์" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "สามารถตั้งค่าเซิร์ฟเวอร์ได้อย่างง่ายดายด้วยวิซาร์ดนี้" + settingsYouMakeHereCanBeChangedLater: "สามารถเปลี่ยนแปลงการตั้งค่าเหล่านี้ในภายหลังได้" + howWillYouUseMisskey: "ต้องการใช้ Misskey อย่างไร?" + _use: + single: "เซิร์ฟเวอร์คนเดียว" + single_description: "ใช้งานเป็นเซิร์ฟเวอร์ส่วนตัวสำหรับตัวเองคนเดียว" + single_youCanCreateMultipleAccounts: "แม้จะใช้งานเป็นเซิร์ฟเวอร์ส่วนตัวสำหรับคนเดียว ก็สามารถสร้างบัญชีผู้ใช้หลายบัญชีได้ตามความจำเป็น" + group: "เซิร์ฟเวอร์กลุ่ม" + group_description: "เชิญผู้ใช้ที่เชื่อถือได้ มาเข้าร่วมใช้งานแบบหลายคน" + open: "เซิร์ฟเวอร์สาธารณะ" + open_description: "เปิดรับผู้ใช้จำนวนมากแบบไม่จำกัด" + openServerAdvice: "การเปิดรับผู้ใช้จำนวนมากมีความเสี่ยง ควรบริหารจัดการด้วยระบบดูแลที่เข้มงวดเพื่อรับมือกับปัญหาที่อาจเกิดขึ้น" + openServerAntiSpamAdvice: "เพื่อป้องกันไม่ให้เซิร์ฟเวอร์ของตนกลายเป็นแหล่งส่งสแปม ควรเปิดใช้งานฟีเจอร์ป้องกันบอต เช่น reCAPTCHA และใส่ใจเรื่องความปลอดภัยอย่างเคร่งครัด" + howManyUsersDoYouExpect: "คาดว่าจะมีผู้ใช้งานประมาณกี่คน?" + _scale: + small: "น้อยกว่า 100 คน (ขนาดเล็ก)" + medium: "เกิน 100 คน แต่น้อยกว่า 1000 คน (ขนาดกลาง)" + large: "เกิน 1000 คน (ขนาดใหญ่)" + largeScaleServerAdvice: "เซิร์ฟเวอร์ขนาดใหญ่อาจต้องการความรู้ด้านโครงสร้างพื้นฐานขั้นสูง เช่น การบาลานซ์โหลด หรือการทำสำเนาฐานข้อมูล" + doYouConnectToFediverse: "เชื่อมต่อกับ Fediverse หรือไม่?" + doYouConnectToFediverse_description1: "หากเชื่อมต่อกับเครือข่ายที่ประกอบด้วยเซิร์ฟเวอร์แบบกระจาย (Fediverse) จะสามารถแลกเปลี่ยนเนื้อหากับเซิร์ฟเวอร์อื่นๆ ได้" + doYouConnectToFediverse_description2: "การเชื่อมต่อกับ Fediverse เรียกว่า “สหพันธ์”" + youCanConfigureMoreFederationSettingsLater: "หลังจากนี้ยังสามารถตั้งค่าแบบขั้นสูง เช่น การกำหนดเซิร์ฟเวอร์ที่อนุญาตให้สหพันธ์ต่อกันได้เพิ่มเติม" + remoteContentsCleaning: "การล้างข้อมูลเนื้อหาที่ได้รับโดยอัตโนมัติ" + remoteContentsCleaning_description: "เมื่อมีการเชื่อมโยงสหพันธ์ จะได้รับเนื้อหาเป็นจำนวนมากอย่างต่อเนื่อง เมื่อเปิดใช้งานการล้างข้อมูลอัตโนมัติ จะทำการลบเนื้อหาเก่าที่ไม่ถูกอ้างอิง ไปจากเซิร์ฟเวอร์โดยอัตโนมัติ เพื่อประหยัดพื้นที่จัดเก็บข้อมูล" + adminInfo: "ข้อมูลผู้ดูแลระบ" + adminInfo_description: "ตั้งค่าข้อมูลผู้ดูแลระบบที่จะใช้รับคำถามและติดต่อ" + adminInfo_mustBeFilled: "หากเปิดใช้เซิร์ฟเวอร์สาธารณะ หรือเปิดใช้งานสหพันธ์ จะต้องกรอกข้อมูลนี้" + followingSettingsAreRecommended: "แนะนำให้ตั้งค่าตามด้านล่างนี้" + applyTheseSettings: "ใช้การตั้งค่านี้" + skipSettings: "ข้ามการตั้งค่า" + settingsCompleted: "การตั้งค่าเสร็จสมบูรณ์แล้ว!" + settingsCompleted_description: "ขอบคุณที่สละเวลามาตั้งค่า ตอนนี้เซิร์ฟเวอร์พร้อมใช้งานได้ทันที" + settingsCompleted_description2: "การตั้งค่าเซิร์ฟเวอร์อย่างละเอียดสามารถทำได้จาก “แผงควบคุม”" + donationRequest: "คำขอรับบริจาค" + _donationRequest: + text1: "Misskey เป็นซอฟต์แวร์ฟรีที่พัฒนาโดยอาสาสมัคร" + text2: "เพื่อให้การพัฒนางานนี้สามารถดำเนินต่อไปได้ในอนาคต หากไม่เป็นการรบกวน รบกวนพิจารณาร่วมสมทบทุนด้วยนะคะ" + text3: "นอกจากนี้ยังมีสิทธิพิเศษสำหรับผู้สนับสนุนอีกด้วยค่ะ" +_uploader: + editImage: "แก้ไขรูปภาพ" + compressedToX: "บีบอัดเป็น {x}" + savedXPercent: "ประหยัดไป {x}%" + abortConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการยกเลิกหรือไม่?" + doneConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการดำเนินการให้เสร็จสิ้นหรือไม่?" + maxFileSizeIsX: "ขนาดไฟล์สูงสุดที่สามารถอัปโหลดได้คือ {x}" + allowedTypes: "ประเภทไฟล์ที่สามารถอัปโหลดได้" + tip: "ยังไม่มีไฟล์ถูกอัปโหลด สามารถ ตรวจสอบ ลบชื่อไฟล์ บีบอัด หรือครอปตัดภาพ ก่อนอัปโหลดได้ในหน้านี้ เมื่อพร้อมแล้วให้กดปุ่ม “อัปโหลด” เพื่อเริ่มการอัปโหลด" +_clientPerformanceIssueTip: + title: "หากรู้สึกว่าแบตเตอรี่หมดเร็ว" + makeSureDisabledAdBlocker: "โปรดปิดการใช้งานตัวบล็อกโฆษณา" + makeSureDisabledAdBlocker_description: "ตัวบล็อกโฆษณาอาจส่งผลต่อประสิทธิภาพ โปรดตรวจสอบว่าไม่ได้เปิดใช้งานผ่านฟังก์ชันของระบบปฏิบัติการ เบราว์เซอร์ หรือส่วนเสริมใดๆ" + makeSureDisabledCustomCss: "โปรดปิดการใช้งาน CSS แบบกำหนดเอง" + makeSureDisabledCustomCss_description: "การเขียนทับสไตล์อาจส่งผลต่อประสิทธิภาพ โปรดตรวจสอบว่าไม่มี CSS แบบกำหนดเองหรือส่วนเสริมที่แก้ไขสไตล์เปิดใช้งานอยู่" + makeSureDisabledAddons: "โปรดปิดการใช้งานส่วนเสริม" + makeSureDisabledAddons_description: "ส่วนเสริมบางตัวอาจรบกวนการทำงานของไคลเอนต์และทำให้ประสิทธิภาพลดลง กรุณาลองปิดส่วนเสริมในเบราว์เซอร์แล้วตรวจสอบอีกครั้ง" +_clip: + tip: "คลิปเป็นฟังก์ชันที่สามารถรวมโน้ตเข้าด้วยกัน" +_userLists: + tip: "สามารถสร้างรายชื่อที่มีผู้ใช้ใดก็ได้ เมื่อสร้างแล้ว รายชื่อนั้นจะแสดงเป็นไทม์ไลน์ได้" +watermark: "ลายน้ำ" +defaultPreset: "พรีเซ็ตเริ่มต้น" +_watermarkEditor: + tip: "สามารถเพิ่มลายน้ำ เช่น ข้อมูลเครดิต ลงในภาพได้" + quitWithoutSaveConfirm: "ต้องการออกโดยไม่บันทึกหรือไม่?" + driveFileTypeWarn: "ไม่รองรับไฟล์นี้" + driveFileTypeWarnDescription: "กรุณาเลือกไฟล์ภาพ" + title: "แก้ไขลายน้ำ" + cover: "ซ้อนทับทั่วทั้งพื้นที่" + repeat: "ปูให้เต็มพื้นที่" + opacity: "ความทึบแสง" + scale: "ขนาด" + text: "ข้อความ" + qr: "QR โค้ด" + position: "ตำแหน่ง" + margin: "ระยะขอบ" + type: "รูปแบบ" + image: "รูปภาพ" + advanced: "ขั้นสูง" + angle: "แองเกิล" + stripe: "ริ้ว" + stripeWidth: "ความกว้างเส้น" + stripeFrequency: "จำนวนเส้น" + polkadot: "ลายจุด" + checker: "ช่องตาราง" + polkadotMainDotOpacity: "ความทึบของจุดหลัก" + polkadotMainDotRadius: "ขนาดของจุดหลัก" + polkadotSubDotOpacity: "ความทึบของจุดรอง" + polkadotSubDotRadius: "ขนาดของจุดรอง" + polkadotSubDotDivisions: "จำนวนจุดรอง" + leaveBlankToAccountUrl: "เว้นว่างไว้หากต้องการใช้ URL ของบัญชีแทน" +_imageEffector: + title: "เอฟเฟกต์" + addEffect: "เพิ่มเอฟเฟกต์" + discardChangesConfirm: "ต้องการทิ้งการเปลี่ยนแปลงแล้วออกหรือไม่?" + _fxs: + chromaticAberration: "ความคลาดสี" + glitch: "กลิตช์" + mirror: "กระจก" + invert: "กลับสี" + grayscale: "ขาวดำเทา" + blur: "มัว" + pixelate: "โมเสก" + colorAdjust: "ปรับแก้สี" + colorClamp: "บีบอัดสี" + colorClampAdvanced: "บีบอัดสี (ขั้นสูง)" + distort: "บิดเบี้ยว" + threshold: "สองสี" + zoomLines: "เส้นความเข้มข้น" + stripe: "ริ้ว" + polkadot: "ลายจุด" + checker: "ช่องตาราง" + blockNoise: "บล็อกที่มีการรบกวน" + tearing: "ฉีกขาด" + fill: "เติมเต็ม" + _fxProps: + angle: "แองเกิล" + scale: "ขนาด" + size: "ขนาด" + radius: "รัศสี" + samples: "จำนวนตัวอย่าง" + offset: "ตำแหน่ง" + color: "สี" + opacity: "ความทึบแสง" + normalize: "นอร์มัลไลซ์" + amount: "จำนวน" + lightness: "สว่าง" + contrast: "คอนทราสต์" + hue: "HUE" + brightness: "ความสว่าง" + saturation: "ความอิ่มตัว" + max: "สูงสุด" + min: "ต่ำสุด" + direction: "ทิศทาง" + phase: "ระยะ" + frequency: "ความถี่" + strength: "ความแรง" + glitchChannelShift: "ความเคลื่อน" + seed: "ซีด" + redComponent: "ส่วนสีแดง" + greenComponent: "ส่วนสีเขียว" + blueComponent: "ส่วนสีน้ำเงิน" + threshold: "เทรชโฮลด์" + centerX: "กลาง X" + centerY: "กลาง Y" + zoomLinesSmoothing: "ทำให้สมูธ" + zoomLinesSmoothingDescription: "ตั้งให้สมูธไม่สามารถใช้ร่วมกับตั้งความกว้างเส้นรวมศูนย์ได้" + zoomLinesThreshold: "ความกว้างเส้นรวมศูนย์" + zoomLinesMaskSize: "ขนาดพื้นที่ตรงกลาง" + zoomLinesBlack: "ทำให้ดำ" + circle: "ทรงกลม" +drafts: "ร่าง" +_drafts: + select: "เลือกฉบับร่าง" + cannotCreateDraftAnymore: "ถึงจำนวนจำกัดที่ฉบับร่างที่สามารถสร้างได้แล้ว" + cannotCreateDraft: "ไม่สามารถสร้างฉบับร่างด้วยเนื้อหานี้ได้" + delete: "ลบฉบับร่าง" + deleteAreYouSure: "ต้องการลบฉบับร่างหรือไม่?" + noDrafts: "ไม่มีฉบับร่าง" + replyTo: "ตอบกลับ {user}" + quoteOf: "อ้างอิงถึงโน้ตของ {user}" + postTo: "โพสต์ไปยัง {channel}" + saveToDraft: "บันทึกเป็นฉบับร่าง" + restoreFromDraft: "คืนค่าจากฉบับร่าง" + restore: "กู้คืน" + listDrafts: "รายการฉบับร่าง" + schedule: "โพสต์กำหนดเวลา" + listScheduledNotes: "รายการโน้ตที่กำหนดเวลาไว้" + cancelSchedule: "ยกเลิกกำหนดเวลา" +qr: "QR โค้ด" +_qr: + showTabTitle: "แสดงผล" + readTabTitle: "แสกน" + shareTitle: "{name}{acct}" + shareText: "โปรดติดตามฉันบน Fediverse ด้วย!" + chooseCamera: "เลือกกล้อง" + cannotToggleFlash: "ไม่สามารถเลือกแสงแฟลชได้" + turnOnFlash: "ปิดแสงแฟลช" + turnOffFlash: "เปิดแสงแฟลช" + startQr: "เริ่มตัวอ่าน QR โค้ด" + stopQr: "หยุดตัวอ่าน QR โค้ด" + noQrCodeFound: "ไม่พบ QR โค้ด" + scanFile: "สแกนภาพจากอุปกรณ์" + raw: "ข้อความ" + mfm: "MFM" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 1756e8dbeb..94e01df0fb 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -1,150 +1,166 @@ --- _lang_: "Türkçe" -headlineMisskey: "Notlarla bağlanmış bir ağ" -introMisskey: "Açık kaynaklı bir dağıtılmış mikroblog hizmeti olan Misskey'e hoş geldiniz.\nMisskey, neler olup bittiğini paylaşmak ve herkese sizden bahsetmek için \"notlar\" oluşturmanıza olanak tanıyan, açık kaynaklı, dağıtılmış bir mikroblog hizmetidir.\nHerkesin notlarına kendi tepkilerinizi hızlıca eklemek için \"Tepkiler\" özelliğini de kullanabilirsiniz👍.\nYeni bir dünyayı keşfedin🚀." -poweredByMisskeyDescription: "name}Açık kaynak bir platform\nMisskeyDünya'nın en sunucularında biri。" -monthAndDay: "{month}Ay {day}Gün" -search: "Arama" -notifications: "Bildirim" +headlineMisskey: "Notlarla birbirine bağlı bir ağ" +introMisskey: "Hoş geldiniz! Misskey, merkezi olmayan bir açık kaynaklı mikroblog platformudur.\n“Notlar” yazarak şu anda neler olduğunu anlatabilir veya olayları başkalarıyla paylaşabilirsiniz. 📡\n“Tepkiler” ile diğer kullanıcıların notları hakkındaki duygularınızı hızlı bir şekilde ifade edebilirsiniz. 👍\nYeni bir dünya sizi bekliyor! 🚀" +poweredByMisskeyDescription: "{name}, açık kaynak platformu Misskey (kısaca “Misskey örneği” olarak anılır) tarafından desteklenen hizmetlerden biridir." +monthAndDay: "{day}.{month}." +search: "Ara" +reset: "Sıfırla" +notifications: "Bildirimler" username: "Kullanıcı Adı" password: "Şifre" -initialPasswordForSetup: "" -forgotPassword: "şifremi unuttum" -fetchingAsApObject: "從聯邦宇宙取得中..." -ok: "TAMAM" -gotIt: "Anladım" -cancel: "İptal" -noThankYou: "Hayır, teşekkürler" -enterUsername: "Kullanıcı adınızı giriniz" -renotedBy: "{user} tarafından Renotelandı" -noNotes: "Notlar mevcut değil." -noNotifications: "Bildirim bulunmuyor" +initialPasswordForSetup: "Kurulum için ilk şifre" +initialPasswordIsIncorrect: "Kurulum için ilk şifre yanlış" +initialPasswordForSetupDescription: "Misskey'i kendiniz kurduysan, yapılandırma dosyasında belirtilen şifreyi kullan.\nMisskey barındırma hizmeti veya benzeri bir hizmet kullanıyorsan, orada belirtilen şifreyi kullan.\nŞifre belirlemediysen, devam etmek için boş bırak." +forgotPassword: "Şifremi unuttum" +fetchingAsApObject: "Fediverse'den talep ediliyor..." +ok: "Tamam" +gotIt: "Anladım!" +cancel: "Vazgeç" +noThankYou: "Hayır, teşekkürler." +enterUsername: "Kullanıcı adı gir" +renotedBy: "{user} renote etti" +noNotes: "Not yok" +noNotifications: "Bildirim yok" instance: "Sunucu" settings: "Ayarlar" notificationSettings: "Bildirim Ayarları" basicSettings: "Temel Ayarlar" otherSettings: "Diğer Ayarlar" -openInWindow: "Bir pencere ile aç" +openInWindow: "Pencerede aç" profile: "Profil" -timeline: "Zaman çizelgesi" -noAccountDescription: "Bu kullanıcı henüz biyografisini yazmadı" -login: "Giriş Yap " -loggingIn: "Oturum aç" +timeline: "Pano" +noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış." +login: "Oturum Aç" +loggingIn: "Giriş Yapılıyor..." logout: "Çıkış Yap" -signup: "Kayıt Ol" -uploading: "Yükleniyor" +signup: "Kaydol" +uploading: "Yükleniyor..." save: "Kaydet" -users: "Kullanıcı" -addUser: "Kullanıcı Ekle" -favorite: "Favoriler" +users: "Kullanıcılar" +addUser: "Kullanıcı ekle" +favorite: "Favori" favorites: "Favoriler" -unfavorite: "Favorilerden Kaldır" -favorited: "Favorilerime eklendi." -alreadyFavorited: "Zaten favorilerinizde kayıtlı." -cantFavorite: "Favorilere kayıt yapılamadı" -pin: "Sabitlenmiş" -unpin: "Sabitlemeyi kaldır" +unfavorite: "Favoriden kaldır" +favorited: "Favoriye eklendi." +alreadyFavorited: "Zaten favoride" +cantFavorite: "Favoriye eklenemedi" +pin: "Profiline sabitle" +unpin: "Profilden sabitlemeyi kaldır" copyContent: "İçeriği kopyala" -copyLink: "Bağlantıyı Kopyala" -copyLinkRenote: "Turkish" +copyLink: "Link kopyala" +copyRemoteLink: "Uzak linki kopyala" +copyLinkRenote: "Renote linkini kopyala" delete: "Sil" deleteAndEdit: "Sil ve yeniden düzenle" -deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota ilişkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir." +deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek ister misin? Bu notla ilgili tüm Tepki, Renote ve Yanıtlar da silinecek." addToList: "Listeye ekle" -addToAntenna: "Antene ekle" -sendMessage: "Mesaj Gönder" -copyRSS: "RSSKopyala" -copyUsername: "Kullanıcı Adını Kopyala" -copyUserId: "KullanıcıyıKopyala" -copyNoteId: "Kimlik notunu kopyala" -copyFileId: "Dosya ID'sini kopyala" -copyFolderId: "Klasör ID'sini kopyala" -copyProfileUrl: "Profil URL'sini kopyala" -searchUser: "Kullanıcıları ara" -reply: "yanıt" -loadMore: "Devamını yükle" -showMore: "Devamını yükle" +addToAntenna: "Anten'e ekle" +sendMessage: "Mesaj gönder" +copyRSS: "RSS kopyala" +copyUsername: "Kullanıcı adını kopyala" +copyUserId: "Kullanıcı ID'yi kopyala" +copyNoteId: "Not ID'yi kopyala" +copyFileId: "Dosya ID'yi kopyala" +copyFolderId: "Klasör ID'yi kopyala" +copyProfileUrl: "Profil URL kopyala" +searchUser: "Kullanıcı ara" +searchThisUsersNotes: "Kullanıcının notlarını ara" +reply: "Yanıtla" +loadMore: "Daha fazla yükle" +showMore: "Daha fazlasını göster" showLess: "Kapat" youGotNewFollower: "seni takip etti" receiveFollowRequest: "Takip isteği alındı" followRequestAccepted: "Takip isteği kabul edildi" mention: "Bahset" mentions: "Bahsetmeler" -directNotes: "Kişisel mesajlar" +directNotes: "Doğrudan notlar" importAndExport: "İçeri/Dışarı aktar" import: "İçeri aktar" export: "Dışa aktar" files: "Dosyalar" download: "İndir" -driveFileDeleteConfirm: "\"{name}\" dosyası silinsin mi? Dosya kullanıldığı tüm notlardan kaybolacaktır." -unfollowConfirm: "{name} takipten çıkarılsın mı?" -exportRequested: "Dışa aktarım talep ettiniz. Bu biraz zaman alabilir. İşlem bitince Sürücünüze eklenecektir." -importRequested: "Dışa aktarım talep ettiniz. Bu işlem biraz zaman alabilir." +driveFileDeleteConfirm: "“{name}” dosyasını silmek istediğinden emin misin? Bu dosyaya ekli tüm notlar da silinecek." +unfollowConfirm: "{name} kullanıcısını cidden takipden çıkmak istiyor musun?" +exportRequested: "Dışa aktarma işlemi talep ettin. Bu işlem biraz zaman alabilir. İşlem tamamlandığında Drive'ına eklenecek." +importRequested: "İçe aktarma talebinde bulundun. Bu işlem biraz zaman alabilir." lists: "Listeler" -noLists: "Liste yok" -note: "not" -notes: "notlar" -following: "takipçi" -followers: "takipçi" -followsYou: "seni takip ediyor" +noLists: "Hiç liste yok" +note: "Not" +notes: "Notlar" +following: "Takip" +followers: "Takipçi" +followsYou: "Sizi takip ediyor" createList: "Liste oluştur" -manageLists: "Yönetici Listeleri" -error: "hata" +manageLists: "Listeleri yönet" +error: "Hata" somethingHappened: "Bir hata oluştu" retry: "Tekrar dene" -pageLoadError: "Sayfa yüklenemedi." -pageLoadErrorDescription: "Bu genelde ağ veya tarayıcı ön belleği hatalarından olur. Lütfen ön belleği temizlemeyi veya birkaç dakika beklemeyi ve sayfayı yenilemeyi deneyin." -serverIsDead: "Sunucu yanıt vermiyor. Birkaç dakika sonra tekrar deneyin." -youShouldUpgradeClient: "Sayfayı görüntülemek için yenileyin." -enterListName: "Liste ismi" +pageLoadError: "Sayfa yüklenirken bir hata oluştu." +pageLoadErrorDescription: "Bu durum genellikle ağ hataları veya tarayıcının önbelleği nedeniyle oluşur. Önbelleği temizleyin ve bir süre bekledikten sonra tekrar dene." +serverIsDead: "Bu sunucu yanıt vermiyor. Lütfen bir süre bekleyin ve tekrar dene." +youShouldUpgradeClient: "Bu sayfayı görüntülemek için lütfen yenileyerek istemcini güncelle." +enterListName: "Listeye bir ad girin" privacy: "Gizlilik" -makeFollowManuallyApprove: "Takip istekleri elle onaylansın" +makeFollowManuallyApprove: "Takip istekleri onay gerektirir" defaultNoteVisibility: "Varsayılan görünürlük" -follow: "takipçi" -followRequest: "Takip isteği" +follow: "Takip et" +followRequest: "Takip isteği gönder" followRequests: "Takip istekleri" -unfollow: "takip etmeyi bırak" -followRequestPending: "Bekleyen Takip Etme Talebi" -enterEmoji: "Emoji Giriniz" -renote: "vazgeçme" -unrenote: "not alma" -renoted: "yeniden adlandırılmış" -cantRenote: "Ayrılamama" -cantReRenote: "not alabilirmiyim" -quote: "alıntı" -inChannelRenote: "Kanal içi Renote" -inChannelQuote: "Kanal içi Alıntı" -pinnedNote: "Sabitlenen" -pinned: "Sabitlenmiş" -you: "sen" -clickToShow: "Görüntülemek için tıkla" -sensitive: "Hassas içerik" +unfollow: "Takibi bırak" +followRequestPending: "Takip isteği beklemede" +enterEmoji: "Bir emoji gir" +renote: "Renote" +unrenote: "Renote geri al" +renoted: "Renote yapıldı." +renotedToX: "{name} adresine Renote" +cantRenote: "Bu not renote edilemez." +cantReRenote: "Renote yeniden Renote edilemez." +quote: "Alıntı" +inChannelRenote: "Kanal içi renote" +inChannelQuote: "Kanal içi alıntı" +renoteToChannel: "Kanala Renote" +renoteToOtherChannel: "Diğer kanala Renote\n" +pinnedNote: "Sabit not" +pinned: "Profiline sabitle" +you: "Sen" +clickToShow: "Göstermek için tıklayın" +sensitive: "Hassas" add: "Ekle" -reaction: "Tepkiler" +reaction: "Tepki" reactions: "Tepkiler" -reactionSettingDescription2: "Sıralamak için sürükleyin, silmek için tıklayın, eklemek için \"+\" tuşuna tıklayın." -rememberNoteVisibility: "Görünürlük ayarlarını hatırla" -attachCancel: "Eki sil" +emojiPicker: "Emoji seçici" +pinnedEmojisForReactionSettingDescription: "Tepki verirken sabitlenecek ve görüntülenecek emojileri ayarlayın." +pinnedEmojisSettingDescription: "Emoji seçiciyi görüntülerken sabitlenecek ve görüntülenecek emojileri ayarlayın" +emojiPickerDisplay: "Emoji seçici ekranı" +overwriteFromPinnedEmojisForReaction: "Tepki ayarlarından geçersiz kılma" +overwriteFromPinnedEmojis: "Genel ayarlardan geçersiz kılma" +reactionSettingDescription2: "Sıralamayı değiştirmek için sürükle, silmek için tıkla, eklemek için “+” tuşuna bas." +rememberNoteVisibility: "Not görünürlük ayarlarını hatırla" +attachCancel: "Eki kaldır" +deleteFile: "Dosyayı sil" markAsSensitive: "Hassas içerik olarak işaretle" unmarkAsSensitive: "Hassas içerik işaretini kaldır" enterFileName: "Dosya ismini gir" mute: "Gizle" unmute: "sesi aç" renoteMute: "sesi kapat" -renoteUnmute: "sesi açmayı iptal et" +renoteUnmute: "Renote sessiz modunu kaldır" block: "engelle" unblock: "engellemeyi kaldır" suspend: "askıya al" -unsuspend: "askıya alma" -blockConfirm: "Onayı engelle" -unblockConfirm: "engellemeyi kaldır onayla" +unsuspend: "askıya almayı kaldır" +blockConfirm: "Engeli onayla" +unblockConfirm: "Engel kaldırmayı onayla" suspendConfirm: "Hesap askıya alınsın mı?" -unsuspendConfirm: "Hesap askıdan kaldırılsın mı" +unsuspendConfirm: "Hesap askıdan kaldırılsın mı?" selectList: "Bir liste seç" editList: "Listeyi düzenle" selectChannel: "Kanal seç" selectAntenna: "Bir anten seç" editAntenna: "Anteni düzenle" +createAntenna: "Bir anten oluşturun" selectWidget: "Araç seç" editWidgets: "Araçları düzenle" editWidgetsExit: "Tamam" @@ -156,308 +172,3110 @@ emojiUrl: "Emoji URL'si" addEmoji: "Emoji ekle" settingGuide: "Önerilen ayarlar" cacheRemoteFiles: "Uzak dosyalar ön belleğe alınsın" -cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenecektir. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracaktır." -youCanCleanRemoteFilesCache: "" +cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenece. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracak." +youCanCleanRemoteFilesCache: "Dosya yönetimi görünümünde 🗑️ düğmesine tıklayarak önbelleği temizleyebilirsin." cacheRemoteSensitiveFiles: "Hassas uzak dosyalar ön belleğe alınsın" -cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecektir." +cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecek." flagAsBot: "Bot olarak işaretle" -flagAsBotDescription: "Bu seçeneği hesap bir program tarafından kontrol ediliyorsa işaretleyin. Bu, diğer geliştiricilerin sonsuz etkileşim zincirleri oluşturmasını engellemeye yardımcı olur ve Misskey'in iç sisteminin hesaba bir bot gibi davranmasını sağlar." +flagAsBotDescription: "Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği etkinleştir. Etkinleştirildiğinde, diğer geliştiriciler için bir işaret görevi görerek diğer botlarla sonsuz etkileşim zincirlerini önleyecek ve Misskey'in iç sistemlerini bu hesabı bir bot olarak ele alacak şekilde ayarlayacak." flagAsCat: "Kedi hesabı" flagAsCatDescription: "Kedi hesabı" -flagShowTimelineReplies: "Zaman akışında notlara gelen cevapları göster" -flagShowTimelineRepliesDescription: "Açık olduğu durumda, zaman akışında kullanıcıların başkalarına verdiği cevaplar gözükür." +flagShowTimelineReplies: "Pano'da notlara gelen cevapları göster" +flagShowTimelineRepliesDescription: "Açık olduğu durumda, Pano'da kullanıcıların başkalarına verdiği cevaplar gözükür." autoAcceptFollowed: "Takip edilen hesapların takip isteklerini kabul et" addAccount: "Hesap ekle" reloadAccountsList: "Hesap listesini güncelle" loginFailed: "Giriş başarısız oldu" showOnRemote: "Uzak sunucuda görüntüle" +continueOnRemote: "Uzak bir sunucuda devam edin" +chooseServerOnMisskeyHub: "Misskey Hub'dan bir sunucu seçin." +specifyServerHost: "Doğrudan bir sunucu ana bilgisayarı belirtin" +inputHostName: "Alan adını girin" general: "Genel" wallpaper: "Duvar kağıdı" setWallpaper: "Duvar kağıdını ayarla" -removeWallpaper: "Duvar kağıdını sil" -searchWith: "Arama: {q}" -youHaveNoLists: "Hiç listeniz yok" -followConfirm: "{name} takip edilsin mi?" -proxyAccount: "Vekil hesabı" -proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi işlevi gören bir hesaptır. Örneğin, bir kullanıcı listeye bir uzak kullanıcı eklediğinde, o kullanıcıyı takip eden yerel bir kullanıcı yoksa uzak kullanıcının etkinliği örneğe teslim edilmeyecektir, dolayısıyla bunun yerine proxy hesabı takip edilecektir." -host: "Sağlayıcı" +removeWallpaper: "Duvar kağıdını kaldır" +searchWith: "Ara: {q}" +youHaveNoLists: "Hiç listeniz yok." +followConfirm: "{name} kullanıcısını takip etmek istediğinden emin misin?" +proxyAccount: "Proxy hesabı" +proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi görevi gören bir hesap. Örneğin, bir kullanıcı listeye uzaktan bir kullanıcı eklediğinde, o kullanıcıyı takip eden yerel kullanıcı yoksa uzaktan kullanıcının etkinliği örneğe iletilmez, bunun yerine proxy hesabı takip eder." +host: "Host" +selectSelf: "Kendimi seç" selectUser: "Kullanıcı seç" -recipient: "Kime" -annotation: "Açıklamalar" +recipient: "Alıcı" +annotation: "Yorumlar" federation: "Federasyon" -instances: "Sunucu" -registeredAt: "Katılma tarihi" -latestRequestReceivedAt: "Alınan son talep" -latestStatus: "En son durum" +instances: "Sunucular" +registeredAt: "Kayıtlı" +latestRequestReceivedAt: "Son talep alındı" +latestStatus: "Son durum" storageUsage: "Depolama kullanımı" -charts: "Çizelgeler" +charts: "Grafikler" perHour: "Saatlik" perDay: "Günlük" -stopActivityDelivery: "Durum güncellemelerini gönderme" +stopActivityDelivery: "Etkinlik göndermeyi durdur" blockThisInstance: "Bu sunucuyu engelle" -silenceThisInstance: "" -operations: "İşlemler" -software: "Yazılımlar" +silenceThisInstance: "Bu sunucuyu sustur" +mediaSilenceThisInstance: "Medya bu sunucuyu sustursun" +operations: "Operasyonlar" +software: "Yazılım" +softwareName: "Yazılım" version: "Sürüm" -metadata: "Meta Verileri" -withNFiles: "{n} tane dosya" +metadata: "Meta veri" +withNFiles: "{n} dosya" monitor: "Monitör" jobQueue: "İşlem sırası" -cpuAndMemory: "İşlemci ve Hafıza" +cpuAndMemory: "CPU ve Bellek" network: "Ağ" disk: "Disk" instanceInfo: "Sunucu Bilgisi" statistics: "İstatistikler" -clearQueue: "Sırayı temizle" -clearQueueConfirmTitle: "Sıra silinsin mi?" -clearQueueConfirmText: "Sırada kalan hiçbir şey iletilmeyecek. Genelde bu işlem gerekli değildir." -clearCachedFiles: "Ön belleği temizle" -clearCachedFilesConfirm: "Ön belleğe alınmış tüm uzak sunucu dosyaları silinsin mi?" -blockedInstances: "Engellenen sunucular" -blockedInstancesDescription: "Engellemek istediğiniz sunucuların alan adlarını satır sonlarıyla ayırarak yazın. Yazılan sunucular bu sunucuyla iletişime geçemeyecek." -silencedInstances: "Turkısh" -silencedInstancesDescription: "" -muteAndBlock: "Susturma ve Engelleme" -mutedUsers: "Susturulan kullanıcılar" +clearQueue: "Kuyruğu temizle" +clearQueueConfirmTitle: "Kuyruğu silmek istediğinden emin misin?" +clearQueueConfirmText: "Kuyrukta kalan teslim edilmemiş notlar birleştirilmeyecek. Genellikle bu işlem gerekli değildir." +clearCachedFiles: "Önbelleği temizle" +clearCachedFilesConfirm: "Tüm önbelleğe alınmış uzak dosyaları silmek istediğinden emin misin?" +blockedInstances: "Engellenen Sunucu" +blockedInstancesDescription: "Engellemek istediğin sunucuların ana bilgisayar adlarını satır sonlarıyla ayırarak liste. Listelenen örnekler artık bu örnekle iletişim kuramayacaktır." +silencedInstances: "Susturulmuş sunucular" +silencedInstancesDescription: "Sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak listele. Listelenen sunuculara ait tüm hesaplar sessize alınmış olarak kabul edilecek ve yalnızca takip isteklerinde bulunabilecek, takip edilmedikleri takdirde yerel hesapları etiketleyemeyeceklerdir. Bu, engellenen sunucuları etkilemeyecek." +mediaSilencedInstances: "Medya susturulmuş sunucular" +mediaSilencedInstancesDescription: "Medya sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak liste. Listelenen sunuculara ait tüm hesaplar hassas hesap olarak değerlendirilecek ve özel emojiler kullanılamayacaktır. Bu durum, engellenen sunucuları etkilemeyecek." +federationAllowedHosts: "Federasyona izin verilen sunucular" +federationAllowedHostsDescription: "Federasyona izin vermek istediğiniz sunucuların ana bilgisayar adlarını satır sonlarıyla ayırın." +muteAndBlock: "Sessize Alma ve Engelleme" +mutedUsers: "Sessize alınan kullanıcılar" blockedUsers: "Engellenen kullanıcılar" noUsers: "Kullanıcı yok" editProfile: "Profili düzenle" -noteDeleteConfirm: "Bu notu silmek istediğinizden emin misiniz?" -pinLimitExceeded: "Daha fazla not sabitlenemez" -intro: "Misskey yüklemesi tamamlandı! Lütfen yönetici hesabını oluşturun." -done: "Tamamlandı" +noteDeleteConfirm: "Bu notu silmek istediğinden emin misin?" +pinLimitExceeded: "Artık daha fazla not sabitleyemezsin" +done: "Tamam" +processing: "İşleniyor..." preview: "Önizleme" default: "Varsayılan" defaultValueIs: "Varsayılan: {value}" -noCustomEmojis: "Emoji bulunamadı" -noJobs: "Hiç işlem yok" -federating: "Federe ediliyor" +noCustomEmojis: "Emoji yok" +noJobs: "Hiç ş yok" +federating: "Birleştirme" blocked: "Engellenmiş" suspended: "Askıya alınmış" all: "Tümü" subscribing: "Abonelik" publishing: "Paylaşım" -notResponding: "Cevap yok" -instanceFollowing: "Sunucuda takip edenler" +notResponding: "Yanıt vermiyor" +instanceFollowing: "Sunucuda takip" instanceFollowers: "Sunucu takipçileri" -instanceUsers: "Sunucu kullanıcıları" +instanceUsers: "Bu sunucunun kullanıcıları" changePassword: "Şifreyi değiştir" security: "Güvenlik" -retypedNotMatch: "Girişler uyuşmuyor." -currentPassword: "Geçerli şifre" +retypedNotMatch: "Girişler eşleşmiyor." +currentPassword: "Mevcut şifre" newPassword: "Yeni şifre" -newPasswordRetype: "Yeni şifre (tekrar)" -attachFile: "Dosya ekle" -more: "Daha!" -featured: "Öne Çıkan" -usernameOrUserId: "Kullanıcı adı veya ID'si" +newPasswordRetype: "Yeni şifreyi tekrar girin" +attachFile: "Dosyaları ekle" +more: "Daha fazlası!" +featured: "Öne çıkan" +usernameOrUserId: "Kullanıcı adı veya ID" noSuchUser: "Kullanıcı bulunamadı" lookup: "Sorgu" announcements: "Duyurular" -imageUrl: "Görsel URL'si" +imageUrl: "Görsel URL" remove: "Sil" removed: "Silindi" -removeAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?" -deleteAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?" -resetAreYouSure: "Sıfırlansın mı?" +removeAreYouSure: "“{x}” öğesini kaldırmak istediğinizden emin misin?" +deleteAreYouSure: "“{x}” öğesini silmek istediğinizden emin misin?" +resetAreYouSure: "Cidden sıfırlansın mı?" +areYouSure: "Emin misin?" saved: "Kaydedildi" upload: "Yükle" -keepOriginalUploading: "Orijinal görseli koru" -keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapatılırsa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." -fromDrive: "Drive Dosyasından" -fromUrl: "Bağlantıdan" -uploadFromUrl: "Bağlantıdan yükle" -uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın bağlantısı" -uploadFromUrlRequested: "Yükleme talep edildi" -uploadFromUrlMayTakeTime: "Yüklemenin tamamlanması biraz süre alabilir." +keepOriginalUploading: "Orijinal görüntüyü koru" +keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapalıysa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." +fromDrive: "Drive'den" +fromUrl: "URL'den" +uploadFromUrl: "URL'den yükle" +uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın URL'si" +uploadFromUrlRequested: "Yükleme istendi" +uploadFromUrlMayTakeTime: "Yükleme işleminin tamamlanması biraz zaman alabilir." +uploadNFiles: "{n} dosya yükle" explore: "Keşfet" -messageRead: "Okundu" -noMoreHistory: "Bundan öncesi yok" -nUsersRead: "{n} kişi okudu" -agreeTo: "Kabul Ediyorum: {0}" -agree: "Kabul Et" -agreeBelow: "Aşağıdakileri kabul ederim" +messageRead: "Oku" +noMoreHistory: "Daha fazla geçmiş bilgisi yok." +startChat: "Sohbete başla" +nUsersRead: "{n} tarafından okundu" +agreeTo: "{0}'ı kabul ediyorum." +agree: "Kabul ediyorum" +agreeBelow: "Aşağıdakileri kabul ediyorum" basicNotesBeforeCreateAccount: "Önemli notlar" -termsOfService: "Şartlar ve Koşullar" +termsOfService: "Hizmet Şartları" start: "Başla" -home: "Ana sayfa" -remoteUserCaution: "Bu kullanıcı bir uzak sunucudan olduğu için alınan bilgiler tam olmayabilir." +home: "Pano" +remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir." activity: "Etkinlik" images: "Görseller" -image: "Görseller" +image: "Görsel" birthday: "Doğum günü" yearsOld: "{age} yaşında" -registeredDate: "Kayıt tarihi" +registeredDate: "Katılma tarihi" location: "Konum" -theme: "Temalar" -themeForLightMode: "Aydınlık Tema" -themeForDarkMode: "Karanlık Tema" +theme: "Tema" +themeForLightMode: "Aydınlık Mod'da kullanılacak tema" +themeForDarkMode: "Karanlık Mod'da kullanılacak tema" light: "Aydınlık" dark: "Karanlık" -lightThemes: "Aydınlık Temalar" -darkThemes: "Karanlık Temalar" -syncDeviceDarkMode: "Sistem Koyu Modu ile senkronize et" -drive: "Sürücü" +lightThemes: "Aydınlık temalar" +darkThemes: "Karanlık temalar" +syncDeviceDarkMode: "Karanlık Modu cihaz ayarlarınızla senkronize et" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" açık. Senkronizasyonu kapatıp modları manuel olarak değiştirmek ister misin?" +drive: "Drive" fileName: "Dosya adı" -selectFile: "Dosya seç" -selectFiles: "Dosya seç" -selectFolder: "Klasör seç" -selectFolders: "Klasör seç" +selectFile: "Dosya seçin" +selectFiles: "Dosyaları seçin" +selectFolder: "Klasör seçin" +selectFolders: "Klasörleri seçin" +fileNotSelected: "Hiç dosya seçilmedi" renameFile: "Dosyayı yeniden adlandır" folderName: "Klasör adı" -createFolder: "Klasör oluştur" -renameFolder: "Klasörü Yeniden Adlandır" -deleteFolder: "Klasörü sil" -addFile: "Dosya ekle" -emptyDrive: "Sürücü boş" +createFolder: "Bir klasör oluşturun" +renameFolder: "Bu klasörü yeniden adlandır" +deleteFolder: "Bu klasörü sil" +folder: "Dosya" +addFile: "Bir dosya ekle" +showFile: "Dosyaları göster" +emptyDrive: "Drive boş" emptyFolder: "Bu klasör boş" -unableToDelete: "Silme mümkün değil" -inputNewFileName: "Yeni dosya ismini girin" -inputNewDescription: "Yeni bir başlık gir" -inputNewFolderName: "Yeni klasör ismini girin" -circularReferenceFolder: "Hedef klasör taşınan klasörün bir alt klasörü." -hasChildFilesOrFolders: "Klasör boş olmadığından silinemiyor" -copyUrl: "URL'yi kopyala" +unableToDelete: "Silinemiyor" +inputNewFileName: "Yeni bir dosya adı girin" +inputNewDescription: "Yeni alternatif metin girin" +inputNewFolderName: "Yeni bir klasör adı girin" +circularReferenceFolder: "Hedef klasör, taşımak istediğiniz klasörün bir alt klasörü." +hasChildFilesOrFolders: "Bu klasör boş olmadığı için silinemez." +copyUrl: "URL kopyala" rename: "Yeniden adlandır" avatar: "Avatar" banner: "Banner" -displayOfSensitiveMedia: "Hassas içerik gösterimi" -whenServerDisconnected: "Sunucu bağlantısı kesildiğinde" -disconnectedFromServer: "Sunucu bağlantısı koptu" +displayOfSensitiveMedia: "Hassas ortamların görüntülenmesi" +whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde" +disconnectedFromServer: "Sunucu bağlantısı kesildi" reload: "Yenile" -doNothing: "Bir şey yapma" -reloadConfirm: "Zaman akışı yenilensin mi?" +doNothing: "Yoksay" +reloadConfirm: "Panoyu yenilemek ister misin?" watch: "İzle" unwatch: "İzlemeyi bırak" accept: "Kabul et" reject: "Reddet" normal: "Normal" -instanceName: "Sunucu ismi" +instanceName: "Sunucu adı" instanceDescription: "Sunucu açıklaması" -maintainerName: "Yönetici ismi" -maintainerEmail: "Yöneticinin e-postası" -tosUrl: "Hizmet Koşulları Bağlantısı" -thisYear: "Bu yıl" -thisMonth: "Bu ay" +maintainerName: "Bakım sorumlusu" +maintainerEmail: "Bakım sorumlusu E-Posta adresi" +tosUrl: "Hizmet Şartları URL'si" +thisYear: "Yıl" +thisMonth: "Ay" today: "Bugün" -monthX: "{month} ay" +dayX: "{day}" +monthX: "{month}" +yearX: "{year}" pages: "Sayfalar" integration: "Entegrasyon" +connectService: "Bağlan" +disconnectService: "Bağlantıyı kes" +enableLocalTimeline: "Yerel Pano'yu etkinleştir" +enableGlobalTimeline: "Global Pano'yu etkinleştir" +disablingTimelinesInfo: "Yöneticiler ve Moderatörler, etkinleştirilmemiş olsalar bile her zaman tüm Pano'ya erişebilecekler." +registration: "Kaydol" +invite: "Davet et" +driveCapacityPerLocalAccount: "Yerel kullanıcı başına Drive kapasitesi" +driveCapacityPerRemoteAccount: "Uzak kullanıcı başına Drive kapasitesi" +inMb: "Megabayt cinsinden" +bannerUrl: "Banner görseli URL" +backgroundImageUrl: "Arka plan görseli URL" basicInfo: "Temel bilgiler" pinnedUsers: "Sabitlenmiş kullanıcılar" -pinnedNotes: "Sabitlenen" -manageAntennas: "Anten ayarları" +pinnedUsersDescription: "“Keşfet” sekmesinde sabitlenecek kullanıcı adlarını satır sonlarıyla ayırarak liste." +pinnedPages: "Sabitlenmiş Sayfalar" +pinnedPagesDescription: "Bu örneğin üst sayfasına sabitlemek istediğin Sayfaların yollarını satır sonlarıyla ayırarak gir." +pinnedClipId: "Sabitlenecek klibin ID" +pinnedNotes: "Sabitlenmiş notlar" +hcaptcha: "hCaptcha" +enableHcaptcha: "hCaptcha etkinleştir" +hcaptchaSiteKey: "Site anahtar" +hcaptchaSecretKey: "Gizli anahtar" +mcaptcha: "mCaptcha" +enableMcaptcha: "mCaptcha etkinleştir" +mcaptchaSiteKey: "Site anahtarı" +mcaptchaSecretKey: "Gizli anahtar" +mcaptchaInstanceUrl: "mCaptcha sunucu URL'si" +recaptcha: "reCAPTCHA" +enableRecaptcha: "reCAPTCHA etkinleştir" +recaptchaSiteKey: "Site anahtar" +recaptchaSecretKey: "Gizli anahtar" +turnstile: "Turnstile" +enableTurnstile: "Turnstile etkinleştir" +turnstileSiteKey: "Site anahtar" +turnstileSecretKey: "Gizli anahtar" +avoidMultiCaptchaConfirm: "Birden fazla Captcha sistemi kullanmak, aralarında çakışmaya neden olabilir. Şu anda etkin olan diğer Captcha sistemlerini devre dışı bırakmak ister misiniz? Etkin kalmalarını istiyorsan, iptal düğmesine bas." +antennas: "Antenler" +manageAntennas: "Antenleri Yönet" +name: "İsim" +antennaSource: "Anten kaynağı" +antennaKeywords: "Dinlenecek anahtar kelimeler" +antennaExcludeKeywords: "Hariç tutulacak anahtar kelimeler" +antennaExcludeBots: "Bot hesaplarını hariç tut" +antennaKeywordsDescription: "VE koşulu için boşluklarla, VEYA koşulu için satır sonlarıyla ayırın." +notifyAntenna: "Yeni notlar hakkında bildirimde bulunun" +withFileAntenna: "Sadece dosyalı notlar" +excludeNotesInSensitiveChannel: "Hassas kanallardan gelen notları hariç tutun" +enableServiceworker: "Tarayıcınız için Push Bildirimlerini Etkinleştir" +antennaUsersDescription: "Satır başına bir kullanıcı adı listele" +caseSensitive: "Harfe duyarlı" +withReplies: "Yanıtları ekle" +connectedTo: "Aşağıdaki hesap(lar) bağlı" +notesAndReplies: "Notlar ve yanıtlar" +withFiles: "Dosyalar dahil" +silence: "Sessize al" +silenceConfirm: "Bu kullanıcıyı susturmak istediğinden emin misin?" +unsilence: "Sessize almayı geri al" +unsilenceConfirm: "Bu kullanıcının sessize alınmasını geri almak istediğinden emin misin?" +popularUsers: "Popüler kullanıcılar" +recentlyUpdatedUsers: "Son zamanlarda aktif olan kullanıcılar" +recentlyRegisteredUsers: "Yeni katılan kullanıcılar" +recentlyDiscoveredUsers: "Yeni keşfedilen kullanıcılar" +exploreUsersCount: "{count} kullanıcı var" +exploreFediverse: "Fediverse'i keşfedin" +popularTags: "Popüler etiketler" userList: "Listeler" -resetPassword: "Şifre sıfırlama" -details: "Detaylar" -deck: "Güverte" -smtpHost: "Sağlayıcı" -smtpUser: "Kullanıcı Adı" +about: "Hakkında" +aboutMisskey: "Misskey Hakkında" +administrator: "Yönetici" +token: "Token" +2fa: "İki faktörlü kimlik doğrulama" +setupOf2fa: "İki faktörlü kimlik doğrulamayı ayarlayın" +totp: "Authenticator Uygulaması" +totpDescription: "Tek seferlik şifreleri girmek için bir kimlik doğrulama uygulaması kullanın" +moderator: "Moderatör" +moderation: "Moderasyon" +moderationNote: "Moderasyon notu" +moderationNoteDescription: "Moderatörler arasında paylaşılacak notları girebilirsin." +addModerationNote: "Moderasyon notu ekle" +moderationLogs: "Moderasyon günlükleri" +nUsersMentioned: "{n} kullanıcı bahsetti" +securityKeyAndPasskey: "Güvenlik ve geçiş anahtarları" +securityKey: "Güvenlik anahtarı" +lastUsed: "Son kullanılan" +lastUsedAt: "Son kullanım: {t}" +unregister: "Kayıttan çık" +passwordLessLogin: "Şifresiz giriş" +passwordLessLoginDescription: "Yalnızca güvenlik anahtarı veya şifre anahtarı kullanarak şifresiz oturum açmaya izin verir." +resetPassword: "Şifreyi sıfırla" +newPasswordIs: "Yeni şifre \"{password}\"" +reduceUiAnimation: "UI animasyonlarını azaltın." +share: "Paylaş" +notFound: "Bulunamadı" +notFoundDescription: "Bu URL'ye karşılık gelen sayfa bulunamadı." +uploadFolder: "Yüklemeler için varsayılan klasör" +markAsReadAllNotifications: "Tüm bildirimleri okundu olarak işaretle" +markAsReadAllUnreadNotes: "Tüm notları okundu olarak işaretle" +markAsReadAllTalkMessages: "Tüm mesajları okundu olarak işaretle" +help: "Yardım" +inputMessageHere: "Mesajınızı buraya girin" +close: "Kapat" +invites: "Davetler" +members: "Üyeler" +transfer: "Transfer" +title: "Başlık" +text: "Metin" +enable: "Etkin" +next: "Sonraki" +retype: "Tekrar girin" +noteOf: "{user} not'u" +quoteAttached: "Alıntı" +quoteQuestion: "Alıntı olarak ekle?" +attachAsFileQuestion: "Panodaki metin uzun. Metin dosyası olarak eklemek ister misin?" +onlyOneFileCanBeAttached: "Bir mesaja yalnızca bir dosya ekleyebilirsin." +signinRequired: "Devam etmeden önce lütfen kayıt olun veya giriş yapın." +signinOrContinueOnRemote: "Devam etmek için sunucunuzu taşıyın veya bu sunucuya kaydolun / giriş yapın." +invitations: "Davetler" +invitationCode: "Davet kodu" +checking: "Kontrol ediliyor..." +available: "Kullanılabilir" +unavailable: "Kullanılamaz" +usernameInvalidFormat: "Büyük ve küçük harfler, rakamlar ve alt çizgi kullanabilirsin. (a~z、A~Z、0~9)" +tooShort: "Çok kısa" +tooLong: "Çok uzun" +weakPassword: "Zayıf şifre" +normalPassword: "Ortalama şifre" +strongPassword: "Güçlü şifre" +passwordMatched: "Eşleşti" +passwordNotMatched: "Eşleşmedi" +signinWith: "{x} ile giriş yapın" +signinFailed: "Giriş yapılamıyor. Girilen kullanıcı adı veya şifre yanlış." +or: "veya" +language: "Dil" +uiLanguage: "Kullanıcı arayüzü dili" +aboutX: "{x} hakkında" +emojiStyle: "Emoji stili" +native: "Yerli" +menuStyle: "Menü stili" +style: "Stil" +drawer: "Çekmece" +popup: "Pop-up" +showNoteActionsOnlyHover: "Not eylemlerini yalnızca üzerine gelindiğinde göster" +showReactionsCount: "Notlardaki tepki sayısını gör" +noHistory: "Geçmiş mevcut değil" +signinHistory: "Giriş geçmişi" +enableAdvancedMfm: "Gelişmiş MFM'yi etkinleştir" +enableAnimatedMfm: "Animasyonlu MFM'yi etkinleştir" +doing: "İşleniyor..." +category: "Kategori" +tags: "Takma adlar" +docSource: "Bu belgenin kaynağı" +createAccount: "Hesap oluştur" +existingAccount: "Mevcut hesap" +regenerate: "Yeniden oluştur" +fontSize: "Yazı tipi boyutu" +mediaListWithOneImageAppearance: "Tek bir resim içeren medya listelerinin yüksekliği" +limitTo: "{x} ile sınırlandır" +noFollowRequests: "Bekleyen takip istekleri yok." +openImageInNewTab: "Görüntüleri yeni sekmede aç" +dashboard: "Gösterge paneli" +local: "Yerel" +remote: "Uzak" +total: "Toplam" +weekOverWeekChanges: "Geçen haftadan beri yapılan değişiklikler" +dayOverDayChanges: "Dünkü değişiklikler" +appearance: "Görünüm" +clientSettings: "İstemci Ayarları" +accountSettings: "Hesap Ayarları" +promotion: "Tanıtım" +promote: "Tanıtıldı" +numberOfDays: "Gün sayısı" +hideThisNote: "Bu notu gizle" +showFeaturedNotesInTimeline: "Pano'da öne çıkan notları göster" +objectStorage: "Nesne Depolama" +useObjectStorage: "Nesne depolamayı kullanın" +objectStorageBaseUrl: "Temel URL" +objectStorageBaseUrlDesc: "Referans olarak kullanılan URL. CDN veya Proxy kullanıyorsanız, bunların URL'sini belirtin.\nS3 için ‘https://.s3.amazonaws.com’ ve GCS veya eşdeğer hizmetler için ‘https://storage.googleapis.com/’ vb. kullanın." +objectStorageBucket: "Kova" +objectStorageBucketDesc: "Lütfen sağlayıcınızda kullanılan kova adını belirtin." +objectStoragePrefix: "Ön ek" +objectStoragePrefixDesc: "Dosyalar bu öneke sahip dizinler altında saklanacaktır." +objectStorageEndpoint: "Uç nokta" +objectStorageEndpointDesc: "AWS S3 kullanıyorsanız bu alanı boş bırakın, aksi takdirde kullandığınız hizmete bağlı olarak uç noktayı ‘’ veya ‘:’ olarak belirtin." +objectStorageRegion: "Bölge" +objectStorageRegionDesc: "'xx-east-1' gibi bir bölge belirt. Hizmetin bölgeler arasında ayrım yapmıyorsa, ‘us-east-1’ girin. AWS yapılandırma dosyalarını veya ortam değişkenlerini kullanıyorsan boş bırak." +objectStorageUseSSL: "SSL kullanın" +objectStorageUseSSLDesc: "API bağlantıları için HTTPS kullanmayacaksanız bunu kapatın." +objectStorageUseProxy: "Proxy üzerinden bağlan" +objectStorageUseProxyDesc: "API bağlantıları için Proxy kullanmayacaksanız bunu kapatın." +objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yapın" +s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." +serverLogs: "Sunucu log kayıtları" +deleteAll: "Tümünü sil" +showFixedPostForm: "Gönderi formunu pano üstünde görüntüle" +showFixedPostFormInChannel: "Gönderi formunu pano üstünde görüntüle (Kanallar)" +withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et" +newNoteRecived: "Yeni Not'lar var" +newNote: "Yeni Not" +sounds: "Sesler" +sound: "Ses" +notificationSoundSettings: "Bildirim sesi ayarları" +listen: "Dinle" +none: "Hiçbiri" +showInPage: "Sayfada göster" +popout: "Açılır pencere" +volume: "Ses hacmi" +masterVolume: "Ana ses seviyesi" +notUseSound: "Sesi kapat" +useSoundOnlyWhenActive: "Misskey etkin olduğunda ses çıkarılır." +details: "Ayrıntılar" +renoteDetails: "Renote detayları" +chooseEmoji: "Bir emoji seçin" +unableToProcess: "İşlem tamamlanamadı." +recentUsed: "Son kullanılan" +install: "Yükle" +uninstall: "Kaldır" +installedApps: "Yetkili Uygulamalar" +nothing: "Burada görülecek bir şey yok." +installedDate: "Yetkili" +lastUsedDate: "En son kullanıldığı tarih" +state: "Durum" +sort: "Sıralama düzeni" +ascendingOrder: "Artan" +descendingOrder: "Azalan" +scratchpad: "Not defteri" +scratchpadDescription: "Scratchpad, AiScript deneyleri için bir ortam sağlar. Misskey ile etkileşim halindeyken yazabilir, çalıştırabilir ve sonuçlarını kontrol edebilirsin." +uiInspector: "UI denetçisi" +uiInspectorDescription: "Bellekteki UI bileşeni sunucu listesini görebilirsin. UI bileşeni, Ui:C: işlevi tarafından oluşturulacak." +output: "Çıktı" +script: "Script" +disablePagesScript: "Sayfalarda AiScript'i devre dışı bırak" +updateRemoteUser: "Uzak kullanıcı bilgilerini güncelle" +unsetUserAvatar: "Avatar'ı kaldır" +unsetUserAvatarConfirm: "Avatarı silmek istediğinden emin misin?" +unsetUserBanner: "Banner'ı kaldır" +unsetUserBannerConfirm: "Banner'ı kaldırmak istediğinden emin misin?" +deleteAllFiles: "Tüm dosyaları sil" +deleteAllFilesConfirm: "Tüm dosyaları silmek istediğinden emin misin?" +removeAllFollowing: "Takip ettiğin tüm kullanıcıları takipten çıkar" +removeAllFollowingDescription: "Bu komutu çalıştırmak, {host} adresindeki tüm hesapları takipten çıkarır. Örneğin, sunucu artık mevcut değilse bu komutu çalıştırın." +userSuspended: "Bu kullanıcı askıya alınmıştır." +userSilenced: "Bu kullanıcı susturuluyor." +yourAccountSuspendedTitle: "Bu hesap askıya alınmıştır." +yourAccountSuspendedDescription: "Bu hesap, sunucunun hizmet şartlarını veya benzerlerini ihlal ettiği için askıya alınmıştır. Daha ayrıntılı bir neden öğrenmek istersen yöneticiyle iletişime geç. Lütfen yeni bir hesap oluşturma." +tokenRevoked: "Geçersiz jeton" +tokenRevokedDescription: "Bu jetonun süresi doldu. Lütfen tekrar giriş yapın." +accountDeleted: "Hesap silindi" +accountDeletedDescription: "Bu hesap silinmiş." +menu: "Menü" +divider: "Bölücü" +addItem: "Öğe Ekle" +rearrange: "Yeniden düzenle" +relays: "Röleler" +addRelay: "Röle ekle" +inboxUrl: "Gelen Kutusu URL" +addedRelays: "Eklenen Röleler" +serviceworkerInfo: "Push bildirimleri için etkinleştirilmeli." +deletedNote: "Silinen not" +invisibleNote: "Görünmez not" +enableInfiniteScroll: "Otomatik olarak daha fazlasını yükle" +visibility: "Görünürlük" +poll: "Anket" +useCw: "İçeriği gizle" +enablePlayer: "Video oynatıcıyı aç" +disablePlayer: "Video oynatıcıyı kapat" +expandTweet: "Notu genişlet" +themeEditor: "Tema düzenleyici" +description: "Açıklama" +describeFile: "Alternatif metin ekle" +enterFileDescription: "Alternatif metin girin" +author: "Yazar" +leaveConfirm: "Kaydedilmemiş değişiklikler var. Bunları silmek istiyor musunuz?" +manage: "Yönetim" +plugins: "Eklentiler" +preferencesBackups: "Tercih yedeklemeleri" +deck: "Deck" +undeck: "Güverteden Ayrıl" +useBlurEffectForModal: "Modaller için bulanıklaştırma efekti kullanın" +useFullReactionPicker: "Tam boy tepki seçiciyi kullanın" +width: "Genişlik" +height: "Yükseklik" +large: "Büyük" +medium: "Orta" +small: "Küçük" +generateAccessToken: "Erişim jetonu oluştur" +permission: "İzinler" +adminPermission: "Yönetici İzinleri" +enableAll: "Tümünü etkinleştir" +disableAll: "Tümünü devre dışı bırak" +tokenRequested: "Hesaba erişim izni ver" +pluginTokenRequestedDescription: "Bu eklenti, burada ayarlanan izinleri kullanabilecek." +notificationType: "Bildirim türü" +edit: "Düzenle" +emailServer: "E-posta sunucusu" +enableEmail: "E-posta dağıtımını etkinleştir" +emailConfigInfo: "Kayıt sırasında veya şifreni unuttuğunda E-postanı doğrulamak için kullanılır." +email: "E-Posta" +emailAddress: "E-Posta adresi" +smtpConfig: "SMTP Sunucu yapılandırması" +smtpHost: "Host" +smtpPort: "Port" +smtpUser: "Kullanıcı adı" smtpPass: "Şifre" +emptyToDisableSmtpAuth: "SMTP kimlik doğrulamasını devre dışı bırakmak için kullanıcı adı ve şifre alanlarını boş bırakın." +smtpSecure: "SMTP bağlantıları için örtük SSL/TLS kullanın" +smtpSecureInfo: "STARTTLS kullanırken bunu kapatın." +testEmail: "Test E-postası gönderimi" +wordMute: "Kelime sustur" +wordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları küçültün. Küçültülmüş notlar, üzerlerine tıklanarak görüntülenebilir." +hardWordMute: "Zorla kelime sustur" +showMutedWord: "Sessize alınan kelimeleri göster" +hardWordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları gizle. Kelime sessize alma özelliğinden farklı olarak, not tamamen görünmez hale gelir." +regexpError: "Düzenli ifade hatası" +regexpErrorDescription: "{tab} kelimesinin {line} satırındaki düzenli ifadede bir hata oluştu:" +instanceMute: "Sunucu Sessizleştirme" +userSaysSomething: "{name} bir şey söyledi." +userSaysSomethingAbout: "{name} “{word}” hakkında bir şey söyledi." +makeActive: "Etkinleştir" +display: "Ekran" +copy: "Kopyala" +copiedToClipboard: "Panoya kopyalandı" +metrics: "Metrikler" +overview: "Genel Bakış" +logs: "Günlükler" +delayed: "Gecikmeli" +database: "Veritabanı" +channel: "Kanallar" +create: "Oluştur" notificationSetting: "Bildirim ayarları" +notificationSettingDesc: "Görüntülemek istediğiniz bildirim türlerini seçin." +useGlobalSetting: "Genel ayarları kullan" +useGlobalSettingDesc: "Etkinleştirildiğinde, hesabınızın bildirim ayarları kullanılır. Devre dışı bırakıldığında, bireysel yapılandırmalar yapılabilir." +other: "Diğer" +regenerateLoginToken: "Giriş jetonunu yeniden oluştur" +regenerateLoginTokenDescription: "Giriş sırasında dahili olarak kullanılan jetonu yeniden oluşturur. Normalde bu işlem gerekli değildir. Yeniden oluşturulursa, tüm cihazlar oturumu kapatılır." +theKeywordWhenSearchingForCustomEmoji: "Bu, kendi emojilerini ararken kullanılan anahtar kelimedir." +setMultipleBySeparatingWithSpace: "Birden fazla girişi boşluklarla ayırın." +fileIdOrUrl: "Dosya ID veya URL" +behavior: "Davranış" +sample: "Örnek" +abuseReports: "Raporlar" +reportAbuse: "Rapor" +reportAbuseRenote: "Raporu yeniden gönder" +reportAbuseOf: "{name} raporu" +fillAbuseReportDescription: "Bu raporla ilgili ayrıntıları lütfen doldur. Belirli bir notla ilgiliyse, lütfen URL'sini de ekle." +abuseReported: "Raporunuz gönderildi. Çok teşekkür ederiz." +reporter: "Raporlayan" +reporteeOrigin: "Bildirim Kaynağı" +reporterOrigin: "Bildirenin Kaynağı" +send: "Gönder" +openInNewTab: "Yeni sekmede aç" +openInSideView: "Yan görünümde aç" +defaultNavigationBehaviour: "Varsayılan gezinme davranışı" +editTheseSettingsMayBreakAccount: "Bu ayarları düzenlemek hesabınıza zarar verebilir." instanceTicker: "Notların sunucu bilgileri" +waitingFor: "{x} bekleniyor" +random: "Rastgele" +system: "Sistem" +switchUi: "UI değiştir" +desktop: "Masaüstü " +clip: "Klip" +createNew: "Yeni oluştur" +optional: "Opsiyonel" +createNewClip: "Klip oluştur" +unclip: "Klip kaldır" +confirmToUnclipAlreadyClippedNote: "Bu not zaten “{name}” klibinin bir parçası. Bu klipten silmek ister misin?" +public: "Herkese açık" +private: "Özel" +i18nInfo: "Misskey, gönüllüler tarafından çeşitli dillere çevrilmektedir. {link} adresinden yardımcı olabilirsin." +manageAccessTokens: "Acces Tokens yönet" +accountInfo: "Hesap bilgileri" +notesCount: "Not sayısı" +repliesCount: "Yanıt sayısı" +renotesCount: "Renote sayısı" +repliedCount: "Alınan yanıt sayısı" +renotedCount: "Alınan Renote sayısı" +followingCount: "Takip sayısı" +followersCount: "Takipçi sayısı" +sentReactionsCount: "Tepki sayısı" +receivedReactionsCount: "Alınan tepki sayısı" +pollVotesCount: "Anket oy sayısı" +pollVotedCount: "Alınan anket oy sayısı" +yes: "Evet" +no: "Hayır" +driveFilesCount: "Drive dosya sayısı" +driveUsage: "Drive alanı kullanımı" +noCrawle: "Tarayıcı indekslemesini reddet" noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et." -clearCache: "Ön belleği temizle" +lockedAccountInfo: "Notunuzun görünürlüğünü “Yalnızca takipçiler” olarak ayarlamadığınız sürece, takipçilerin manuel olarak onaylanmasını gerektirse bile notlarınız herkes tarafından görülebilir." +alwaysMarkSensitive: "Varsayılan olarak hassas olarak işaretle" +loadRawImages: "Küçük resimleri göstermek yerine orijinal resimleri yükle" +disableShowingAnimatedImages: "Animasyonlu görüntüleri oynatmayın" +highlightSensitiveMedia: "Hassas medyayı vurgulayın" +verificationEmailSent: "Doğrulama e-postası gönderildi. Doğrulamayı tamamlamak için e-postadaki bağlantıyı takip edin." +notSet: "Ayarlı değil" +emailVerified: "E-posta adresi doğrulandı." +noteFavoritesCount: "Favori not sayısı" +pageLikesCount: "Beğenilen sayfa sayısı" +pageLikedCount: "Alınan sayfa beğen sayısı" +contact: "İletişim" +useSystemFont: "Sistemin varsayılan yazı tipini kullanın" +clips: "Klipler" +experimentalFeatures: "Deneysel özellikler" +experimental: "Deneysel" +thisIsExperimentalFeature: "Bu deneysel bir özellik. İşlevselliği değişebilir ve amaçlandığı gibi çalışmayabilir." +developer: "Geliştirici" +makeExplorable: "Hesabı “Keşfet” bölümünde görünür hale getir" +makeExplorableDescription: "Bunu kapatırsanız, hesabınız “Keşfet” bölümünde görünmez." +duplicate: "Çoğalt" +left: "Sol" +center: "Merkez" +wide: "Geniş" +narrow: "Dar" +reloadToApplySetting: "Bu ayar, sayfa yeniden yüklendikten sonra geçerli olacaktır. Şimdi yeniden yüklemek ister misin?" +needReloadToApply: "Bunun yansıtılması için yeniden yükleme yapılması gerekir." +needToRestartServerToApply: "Değişikliğin yansıtılması için Misskey'in yeniden başlatılması gerekir." +showTitlebar: "Başlık çubuğunu göster" +clearCache: "Önbellek temizle" onlineUsersCount: "{n} kullanıcı çevrim içi" +nUsers: "{n} Kullanıcı" +nNotes: "{n} Not" +sendErrorReports: "Hata raporları gönder" +sendErrorReportsDescription: "Etkinleştirildiğinde, bir sorun oluştuğunda ayrıntılı hata bilgileri Misskey ile paylaşılacak ve bu da Misskey'in kalitesinin iyileştirilmesine yardımcı olacak.\nBu bilgiler arasında işletim sisteminizin sürümü, kullandığınız tarayıcı, Misskey'deki faaliyetlerin vb. yer alacaktır." +myTheme: "Benim temam" +backgroundColor: "Arka plan rengi" +accentColor: "Vurgu rengi" +textColor: "Metin rengi" +saveAs: "Farklı kaydet" +advanced: "Gelişmiş" +advancedSettings: "Gelişmiş ayarlar" +value: "Değer" +createdAt: "Oluşturuldu" +updatedAt: "Güncellendi" +saveConfirm: "Değişiklikleri kaydetmek ister misin?" +deleteConfirm: "Cidden silmek istiyor musunuz?" +invalidValue: "Geçersiz değer." +registry: "Kayıt Defteri" +closeAccount: "Hesabı kapat" +currentVersion: "Şu anki sürüm" +latestVersion: "En yeni sürüm" +youAreRunningUpToDateClient: "İstemci yazılımınızın en yeni sürümünü kullanıyorsunuz." +newVersionOfClientAvailable: "İstemcinin daha yeni bir sürümü var." +usageAmount: "Kullanım" +capacity: "Kapasite" +inUse: "Kullanılıyor" +editCode: "Kodu düzenle" +apply: "Uygula" +receiveAnnouncementFromInstance: "Bu sunucudan bildirimler alın" +emailNotification: "E-posta bildirimi" +publish: "Yayınla" +inChannelSearch: "Kanalda ara" +useReactionPickerForContextMenu: "Sağ tıklama ile tepki seçiciyi aç" +typingUsers: "{users} yazıyor..." +jumpToSpecifiedDate: "Belirli bir tarihe atla" +showingPastTimeline: "Şu anda eski bir Pano görüntüleniyor." +clear: "Temizle" +markAllAsRead: "Tümünü okundu olarak işaretle" +goBack: "Geri" +unlikeConfirm: "Cidden beğenini kaldırmak mı istiyorsun?" +fullView: "Tam görünüm" +quitFullView: "Tam ekranı kapat" +addDescription: "Açıklama ekle" +userPagePinTip: "Bireysel notların menüsünden “Profiline sabitle” seçeneğini seçerek notları burada görüntüleyebilirsin." +notSpecifiedMentionWarning: "Bu notta, alıcılar arasında yer almayan kullanıcılar hakkında bilgiler bulunmaktadır." +info: "Hakkında" +userInfo: "Kullanıcı hakkında" +unknown: "Bilinmiyor" +onlineStatus: "Çevrimiçi durumu" +hideOnlineStatus: "Çevrimiçi durumunu gizle" +hideOnlineStatusDescription: "Çevrimiçi durumunuzu gizlemek, arama gibi bazı özelliklerin kullanışlılığını azaltır." +online: "Online" +active: "Aktif" +offline: "Offline" +notRecommended: "Tavsiye edilmez" +botProtection: "Bot Koruması" +instanceBlocking: "Blocked/Silenced Instances" +selectAccount: "Hesap seç" +switchAccount: "Hesap değiştir" +enabled: "Aktif" +disabled: "Devre Dışı" +quickAction: "Hızlı eylemler" user: "Kullanıcı" -global: "Küresel" +administration: "Yönetim" +accounts: "Hesaplar" +switch: "Anahtar" +noMaintainerInformationWarning: "Bakımcı bilgileri yapılandırılmamıştır." +noInquiryUrlWarning: "Sorgu URL'si ayarlanmadı" +noBotProtectionWarning: "Bot koruması yapılandırılmamıştır." +configure: "Yeniden Yapılandır" +postToGallery: "Yeni galeri gönderisi oluştur" +postToHashtag: "Bu hashtag'e gönder" +gallery: "Galeri" +recentPosts: "Son gönderiler" +popularPosts: "Popüler gönderiler" +shareWithNote: "Notla paylaş" +ads: "Reklamlar" +expiration: "Son tarih" +startingperiod: "Başla" +memo: "Hatırlatıcı" +priority: "Öncelik" +high: "Yüksek" +middle: "Orta" +low: "Düşük" +emailNotConfiguredWarning: "E-posta adresi ayarlanmamış." +ratio: "Oran" +previewNoteText: "Önizlemeyi göster" +customCss: "Özel CSS" +customCssWarn: "Bu ayar, yalnızca ne işe yaradığını biliyorsanız kullanılmalıdır. Yanlış değerler girilmesi, istemcinin normal şekilde çalışmamasına neden olabilir." +global: "Global" squareAvatars: "Kare avatarlar" +sent: "Gönderilen" +received: "Alınan" +searchResult: "Arama sonuçları" +hashtags: "Hashtag'ler" +troubleshooting: "Sorun Giderme" +useBlurEffect: "UI'da bulanıklık efektleri kullanın" +learnMore: "Daha fazla bilgi edinin" +misskeyUpdated: "Misskey güncellendi!" +whatIsNew: "Değişiklikleri göster" +translate: "Çevir" +translatedFrom: "{x}'ten çevrilmiştir." +accountDeletionInProgress: "Hesap silme işlemi şu anda devam ediyor." +usernameInfo: "Bu sunucudaki diğer hesaplardan hesabını ayıran bir isim. Alfabe (a~z, A~Z), rakamlar (0~9) veya alt çizgi (_) kullanabilirsin. Kullanıcı adları daha sonra değiştirilemez." +aiChanMode: "Ai Modu" +devMode: "Geliştirici modu" +keepCw: "İçerik uyarılarını sakla" +pubSub: "Yayın/Abonelik Hesapları" +lastCommunication: "Son iletişim" +resolved: "Çözülmüş" +unresolved: "Çözülmemiş" +breakFollow: "Takipçiyi kaldır" +breakFollowConfirm: "Bu takipçiyi ciddden silmek istiyor musun?" +itsOn: "Etkin" +itsOff: "Devre Dışı" +on: "Açık" +off: "Kapalı" +emailRequiredForSignup: "Kayıt için E-posta adresi gereklidir." +unread: "Okunmamış" +filter: "Filtre" +controlPanel: "Kontrol Paneli" +manageAccounts: "Hesapları Yönet" +makeReactionsPublic: "Tepki geçmişini herkese açık olarak ayarla" +makeReactionsPublicDescription: "Bu, geçmişteki tüm tepkilerinin listesini herkese açık hale getirecek." +classic: "Klasik" +muteThread: "Konuyu sessize al" +unmuteThread: "Konuyu sessizden çıkar" +followingVisibility: "Takip edilenlerin görünürlüğü" +followersVisibility: "Takipçilerin görünürlüğü" +continueThread: "Konunun devamını görüntüle" +deleteAccountConfirm: "Bu, hesabını geri dönüşü olmayan bir şekilde silecek. Devam etmek istiyor musun?" +incorrectPassword: "Yanlış şifre." +incorrectTotp: "Tek kullanımlık şifre yanlış veya süresi dolmuş." +voteConfirm: "\"{choice}\" için oyunuzu onaylıyor musunuz?" +hide: "Gizle" +useDrawerReactionPickerForMobile: "Mobil cihazlarda tepki seçiciyi çekmece olarak göster" +welcomeBackWithName: "Hoş geldin, {name}" +clickToFinishEmailVerification: "E-posta doğrulamasını tamamlamak için lütfen [{ok}] düğmesine tıklayın." +overridedDeviceKind: "Cihaz türü" +smartphone: "Akıllı telefon" +tablet: "Tablet" +auto: "Otomatik" +themeColor: "Örnek Ticker Rengi" +size: "Boyut" +numberOfColumn: "Sütun sayısı" searchByGoogle: "Arama" +instanceDefaultLightTheme: "Sunucu genelinde varsayılan açık tema" +instanceDefaultDarkTheme: "Sunucu genelinde varsayılan koyu tema" +instanceDefaultThemeDescription: "Tema kodunu nesne biçiminde girin." +mutePeriod: "Sessiz kalma süresi" +period: "Zaman sınırı" +indefinitely: "Kalıcı olarak" +tenMinutes: "10 dakika" +oneHour: "1 saat" +oneDay: "1 gün" +oneWeek: "1 hafta" +oneMonth: "1 ay" +threeMonths: "3 ay" +oneYear: "1 yıl" +threeDays: "3 gün" +reflectMayTakeTime: "Bunun yansıtılması biraz zaman alabilir." +failedToFetchAccountInformation: "Hesap bilgileri alınamadı" +rateLimitExceeded: "Hız sınırı aşıldı" +cropImage: "Görüntüyü kırp" +cropImageAsk: "Bu görüntüyü kırpmak ister misin?" +cropYes: "Kırp" +cropNo: "Olduğu gibi kullanın" file: "Dosyalar" +recentNHours: "Son {n} saat" +recentNDays: "Son {n} gün" +noEmailServerWarning: "E-posta sunucusu yapılandırılmamış." +thereIsUnresolvedAbuseReportWarning: "Çözülmemiş raporlar var." +recommended: "Önerilen" +check: "Kontrol" +driveCapOverrideLabel: "Bu kullanıcının Drive kapasitesini değiştir" +driveCapOverrideCaption: "Kapasiteyi varsayılan değere sıfırlamak için 0 veya daha düşük bir değer girin." +requireAdminForView: "Bunu görüntülemek için yönetici hesabıyla oturum açmanız gerekir." +isSystemAccount: "Sistem tarafından oluşturulan ve otomatik olarak işletilen bir hesap." +typeToConfirm: "Onaylamak için lütfen {x} girin." +deleteAccount: "Hesabı sil" +document: "Dokümantasyon" +numberOfPageCache: "Önbelleğe alınmış sayfa sayısı" +numberOfPageCacheDescription: "Bu sayıyı artırmak, kullanıcının cihazında daha fazla bellek kullanımı nedeniyle daha fazla yük oluşturmakla birlikte, kullanıcının rahatlığını artıracaktır." +logoutConfirm: "Çıkmak istediğinden emin misin?" +logoutWillClearClientData: "Oturumu kapatmak, tarayıcıdan istemcinin ayarlarını siler. Tekrar oturum açtığında ayarları geri yükleyebilmek için, ayarlarının otomatik yedeklenmesini etkinleştirmen gerekir." +lastActiveDate: "Son kullanımı" +statusbar: "Durum çubuğu" +pleaseSelect: "Bir seçenek seçin" +reverse: "Tersine" +colored: "Renkli" +refreshInterval: "Güncelleme aralığı" +label: "Etiket" +type: "Tür" +speed: "Hız" +slow: "Yavaş" +fast: "Hızlı" +sensitiveMediaDetection: "Hassas ortamların tespiti" +localOnly: "Yalnızca yerel" +remoteOnly: "Sadece uzaktan" +failedToUpload: "Yükleme başarısız" +cannotUploadBecauseInappropriate: "Bu dosya, dosyanın bazı kısımlarının uygunsuz olabileceği tespit edildiği için yüklenemiyor." +cannotUploadBecauseNoFreeSpace: "Drive kapasitesi yetersiz olduğu için yükleme başarısız oldu." +cannotUploadBecauseExceedsFileSizeLimit: "Bu dosya, dosya boyutu sınırını aştığı için yüklenemiyor." +cannotUploadBecauseUnallowedFileType: "Yetkisiz dosya türü nedeniyle yükleme yapılamıyor." +beta: "Beta" +enableAutoSensitive: "Otomatik olarak hassas olarak işaretleme" +enableAutoSensitiveDescription: "Mümkün olduğunda, Makine Öğrenimi yoluyla hassas ortamların otomatik olarak algılanmasını ve işaretlenmesini sağlar. Bu seçenek devre dışı bırakılmış olsa bile, örnek genelinde etkinleştirilebilir." +activeEmailValidationDescription: "E-posta adreslerinin daha sıkı bir şekilde doğrulanmasını sağlar. Bu, tek kullanımlık adreslerin kontrol edilmesini ve adresin gerçekten iletişim kurulabilir olup olmadığının kontrol edilmesini içerir. İşaretlenmediğinde, yalnızca e-postanın biçimi doğrulanır." +navbar: "Gezinti çubuğu" +shuffle: "Karıştır" +account: "Hesap" +move: "Taşı" pushNotification: "Push bildirimleri" subscribePushNotification: "Push bildirimlerini etkinleştir" unsubscribePushNotification: "Push bildirimlerini kapat" pushNotificationAlreadySubscribed: "Push bildirimleri zaten açık" pushNotificationNotSupported: "Push bildirimleri sunucu veya tarayıcı tarafından desteklenmiyor" +sendPushNotificationReadMessage: "Okunduktan sonra push bildirimlerini silin" +sendPushNotificationReadMessageCaption: "Bu, cihazınızın güç tüketimini artırabilir." +windowMaximize: "Maksimize et" +windowMinimize: "Minimize et" +windowRestore: "Geri yükle" +caption: "Alternatif metin" +loggedInAsBot: "Şu anda bot olarak oturum açmış durumdasınız." +tools: "Araçlar" +cannotLoad: "Yüklenemiyor" +numberOfProfileView: "Profil görüntülemeleri" +like: "Beğen" +unlike: "Beğenme" +numberOfLikes: "Beğeniler" +show: "Göster" +neverShow: "Bir daha gösterme" +remindMeLater: "Belki daha sonra" +didYouLikeMisskey: "Misskey'i sevdin mi?" +pleaseDonate: "{host} ücretsiz yazılım Misskey kullanmaktadır. Misskey'in geliştirilmesinin devam edebilmesi için bağışlarınızı çok takdir ederiz!" +correspondingSourceIsAvailable: "İlgili kaynak kodu {anchor} adresinde mevcuttur." +roles: "Roller" +role: "Rol" noRole: "Rol bulunamadı" +normalUser: "Normal kullanıcı" +undefined: "Tanımlanmamış" +assign: "Atama" +unassign: "Atamayı kaldır" color: "Renk" +manageCustomEmojis: "Özel Emojileri Yönet" +manageAvatarDecorations: "Avatar süslerini yönet" +youCannotCreateAnymore: "Oluşturma sınırına ulaştınız." +cannotPerformTemporary: "Geçici olarak kullanılamıyor" +cannotPerformTemporaryDescription: "Bu işlem, yürütme sınırını aştığı için geçici olarak gerçekleştirilememekte. Lütfen bir süre bekle ve tekrar dene." +invalidParamError: "Geçersiz parametreler" +invalidParamErrorDescription: "İstek parametreleri geçersiz. Bu durum genellikle bir hata nedeniyle oluşur, ancak boyut sınırlarını aşan girdiler veya benzer nedenlerden de kaynaklanabilir." +permissionDeniedError: "İşlem reddedildi" +permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir." +preset: "Ön ayar" +selectFromPresets: "Ön ayarlardan seçim yapın" +custom: "Özel" +achievements: "Başarılar" +gotInvalidResponseError: "Geçersiz sunucu yanıtı" +gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." +thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." +thisPostMayBeAnnoyingHome: "Ana panoya gönder" +thisPostMayBeAnnoyingCancel: "İptal" +thisPostMayBeAnnoyingIgnore: "Yine de gönder" +collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" +collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notları kapat." +internalServerError: "İç Sunucu Hatası" +internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı." +copyErrorInfo: "Hata ayrıntılarını kopyala" +joinThisServer: "Kaydol" +exploreOtherServers: "Diğer sunucuları keşfet" +letsLookAtTimeline: "Pano'ya bir göz atın" +disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?" +disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez." +disableFederationOk: "Devre Dışı" +invitationRequiredToRegister: "Bu etkinlik davetle katılımlıdır. Geçerli bir davet kodu girerek kaydolmanız gerekir." +emailNotSupported: "Bu sunucu, E-Posta göndermeyi desteklemiyor." +postToTheChannel: "Kanalına gönder" +cannotBeChangedLater: "Bu daha sonra değiştirilemez." +reactionAcceptance: "Tepki Kabulü" +likeOnly: "Sadece beğeniler" +likeOnlyForRemote: "Tüm (Yalnızca uzak sunucu için beğeniler)" +nonSensitiveOnly: "Hassas olmayanlar için" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Yalnızca hassas olmayanlar (Yalnızca uzaktan beğeniler)" +rolesAssignedToMe: "Bana atanan roller" +resetPasswordConfirm: "Şifreni gerçekten sıfırlamak istiyor musun?" +sensitiveWords: "Hassas kelimeler" +sensitiveWordsDescription: "Yapılandırılan kelimelerden herhangi birini içeren tüm notların görünürlüğü otomatik olarak “Ana Sayfa” olarak ayarlanacaktır. Satır sonları ile ayırarak birden fazla not listeleyebilirsin." +sensitiveWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anahtar kelimeleri eğik çizgi ile çevrelemek bunları düzenli ifadeye dönüştürür." +prohibitedWords: "Yasaklanmış kelimeler" +prohibitedWordsDescription: "Belirlenen kelime(ler)i içeren bir not göndermeye çalışıldığında hata verir. Birden fazla kelime, yeni satırla ayrılmış olarak ayarlanabilir." +prohibitedWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anahtar kelimeleri eğik çizgi ile çevrelemek bunları düzenli ifadeye dönüştürür." +hiddenTags: "Gizli hashtag'ler" +hiddenTagsDescription: "Trend listesinde gösterilmeyecek etiketleri seçin.\nSatırlarla birden fazla etiket kaydedilebilir." +notesSearchNotAvailable: "Not arama özelliği kullanılamıyor." +usersSearchNotAvailable: "Kullanıcı araması mevcut değildir." +license: "Lisans" +unfavoriteConfirm: "Cidden favorilerden kaldırmak istiyor musunuz?" +myClips: "Kliplerim" +drivecleaner: "Drive Temizleyici" +retryAllQueuesNow: "Tüm kuyrukları yeniden çalıştırmayı deneyin" +retryAllQueuesConfirmTitle: "Cidden hepsini tekrar denemek istiyor musunuz?" +retryAllQueuesConfirmText: "Bu, sunucu yükünü geçici olarak artıracaktır." +enableChartsForRemoteUser: "Uzak kullanıcı veri grafikleri oluşturun" +enableChartsForFederatedInstances: "Uzak sunucu veri grafikleri oluşturun" +enableStatsForFederatedInstances: "Uzak sunucu istatistiklerini alın" +showClipButtonInNoteFooter: "Not eylem menüsüne “Klip” ekle" +reactionsDisplaySize: "Tepki ekran boyutu" +limitWidthOfReaction: "Tepkilerin maksimum genişliğini sınırla ve bunları küçültülmüş boyutta görüntüle." +noteIdOrUrl: "Not ID veya URL" +video: "Video" +videos: "Videolar" +audio: "Ses" +audioFiles: "Ses Dosyası" +dataSaver: "Veri Tasarrufu" +accountMigration: "Hesap Taşıma" +accountMoved: "Bu kullanıcı yeni bir hesaba taşındı:" +accountMovedShort: "Bu hesap taşınmıştır." +operationForbidden: "İşlem yasak" +forceShowAds: "Her zaman reklamları göster" addMemo: "Kısa not ekle" +editMemo: "Kısa not düzenle" +reactionsList: "Tepkiler" +renotesList: "Renote'lar" +notificationDisplay: "Bildirimler" +leftTop: "Sol üst" +rightTop: "Sağ üst" +leftBottom: "Sol alt" +rightBottom: "Sağ alt" +stackAxis: "Yığınlama yönü" +vertical: "Dikey" +horizontal: "Yatay" +position: "Pozisyon" +serverRules: "Sunucu kuralları" +pleaseConfirmBelowBeforeSignup: "Bu sunucuya kaydolmak için aşağıdakileri gözden geçirip kabul etmelisin:" +pleaseAgreeAllToContinue: "Devam etmek için yukarıdaki tüm alanları kabul etmelisin." +continue: "Devam et" +preservedUsernames: "Rezerve edilmiş kullanıcı adları" +preservedUsernamesDescription: "Rezervasyon yapmak için kullanıcı adlarını satır sonlarıyla ayırarak listele. Bu kullanıcı adları normal hesap oluşturma sırasında kullanılamaz hale gelir, ancak yöneticiler tarafından manuel olarak hesap oluşturmak için kullanılabilir. Bu kullanıcı adlarını kullanan mevcut hesaplar etkilenmez." +createNoteFromTheFile: "Bu dosyadan not oluşturun" +archive: "Arşiv" +archived: "Arşivle" +unarchive: "Arşivden çıkar" +channelArchiveConfirmTitle: "Cidden {name} arşivlemek mi istiyorsun?" +channelArchiveConfirmDescription: "Arşivlenmiş bir kanal artık kanal listesinde veya arama sonuçlarında görünmeyecektir. Ayrıca, bu kanala yeni gönderiler eklenemeyecek." +thisChannelArchived: "Bu kanal arşivlenmiş." +displayOfNote: "Not ekranı" +initialAccountSetting: "Profil ayarları" +youFollowing: "Takip edildi" +preventAiLearning: "Makine Öğreniminde (Üretken Ai) kullanımını reddet" +preventAiLearningDescription: "Tarayıcılardan, makine öğrenimi (Tahminsel / Üretken Ai) veri kümelerinde yayınlanan metin veya görsel materyalleri vb. kullanmamalarını talep eder. Bu, ilgili içeriğe “noai” HTML-Response bayrağı eklenerek gerçekleştirilir. Ancak, bu bayrakla tam bir önleme sağlanamaz, çünkü bu bayrak basitçe göz ardı edilebilir." +options: "Seçenekler" +specifyUser: "Belirli kullanıcı" +lookupConfirm: "Yukarı bakmak ister misin?" +openTagPageConfirm: "Bir hashtag sayfası açmak ister misin?" +specifyHost: "Belirli ana bilgisayar" +failedToPreviewUrl: "Önizleme yapılamadı" +update: "Güncelle" +rolesThatCanBeUsedThisEmojiAsReaction: "Bu emojiyi tepki olarak kullanabileceğin roller" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Herhangi bir rol belirtilmezse, herkes bu emojiyi tepki olarak kullanabilir." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Bu roller herkese açık olmalıdır." +cancelReactionConfirm: "Tepkini cidden silmek istiyor musun?" +changeReactionConfirm: "Tepkini cidden değiştirmek istiyor musun?" +later: "Daha sonra" +goToMisskey: "Misskey'e" +additionalEmojiDictionary: "Ek emoji sözlükleri" +installed: "Yüklendi" +branding: "Markalaşma" +enableServerMachineStats: "Sunucu donanım istatistiklerini yayınla" +enableIdenticonGeneration: "Kullanıcı identicon oluşturmayı etkinleştir" +turnOffToImprovePerformance: "Devre dışı bırakma, daha yüksek performansa yol açabilir." +createInviteCode: "Davet Kodu oluştur" +createWithOptions: "Seçeneklerle oluştur" +createCount: "Davet sayısı" +inviteCodeCreated: "Davet oluşturuldu" +inviteLimitExceeded: "Oluşturulabilecek davetiyelerin maksimum sayısına ulaştın." +createLimitRemaining: "{limit} Davet limiti kaldı" +inviteLimitResetCycle: "Bu limit {time} tarihinde {limit} değerine sıfırlanacaktır." +expirationDate: "Son kullanma tarihi" +noExpirationDate: "Son kullanma tarihi yok" +inviteCodeUsedAt: "Kullanılan davet kodu" +registeredUserUsingInviteCode: "Kullanılan davet" +waitingForMailAuth: "E-Posta doğrulama beklemede" +inviteCodeCreator: "Davet oluşturuldu" +usedAt: "Kullanıldığı yer" +unused: "Kullanılmamış" +used: "Kullanılmış" +expired: "Süresi dolmuş" +doYouAgree: "Katılıyor musunuz?" +beSureToReadThisAsItIsImportant: "Lütfen bu önemli bilgileri okuyun." +iHaveReadXCarefullyAndAgree: "“{x}” metnini okudum ve kabul ediyorum." +dialog: "Diyalog" icon: "Avatar" -replies: "yanıt" -renotes: "vazgeçme" +forYou: "Senin için" +currentAnnouncements: "Güncel duyurular" +pastAnnouncements: "Geçmiş duyurular" +youHaveUnreadAnnouncements: "Okunmamış duyurular var." +useSecurityKey: "Güvenlik anahtarını veya şifreni kullanmak için lütfen tarayıcının veya cihazının talimatlarını izle." +replies: "Yanıtla" +renotes: "Renote'lar" +loadReplies: "Yanıtları göster" +loadConversation: "Konuşmayı göster" +pinnedList: "Sabitlenmiş liste" +keepScreenOn: "Ekranı açık tut" +verifiedLink: "Bağlantı sahipliği doğrulanmıştır." +notifyNotes: "Yeni notlar hakkında bildirimde bulun" +unnotifyNotes: "Yeni notlar hakkında bildirim almayı durdur" +authentication: "Kimlik doğrulama" +authenticationRequiredToContinue: "Devam etmek için lütfen kimlik doğrulaması yapın." +dateAndTime: "Zaman damgası" +showRenotes: "Renote'ları göster" +edited: "Düzenlendi" +notificationRecieveConfig: "Bildirim Ayarları" +mutualFollow: "Karşılıklı takip" +followingOrFollower: "Takip eden veya takipçi" +fileAttachedOnly: "Yalnızca dosya içeren notlar" +showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları göster" +hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" +showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" +hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" +confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını panoda diğer kullanıcılara göstermek istiyor musun?" +confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını panoda cidden göstermeyecek misin?" +externalServices: "Dış Hizmetler" +sourceCode: "Kaynak kodu" +sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." +repositoryUrl: "Depo URL'si" +repositoryUrlDescription: "Misskey'i olduğu gibi kullanıyorsanız (kaynak kodunda herhangi bir değişiklik yapmadan), https://github.com/misskey-dev/misskey adresini girin." +repositoryUrlOrTarballRequired: "Bir depo yayınlamadıysanız, bunun yerine bir tarball sağlamalısınız. Daha fazla bilgi için .config/example.yml dosyasına bakın." +feedback: "Feedback" +feedbackUrl: "Geri Bildirim URL'si" +impressum: "Yayıncı Bilgileri" +impressumUrl: "Yayıncı Bilgileri URL'si" +impressumDescription: "Almanya gibi bazı ülkelerde, ticari web sitelerinde işletmeci iletişim bilgilerinin (Yayıncı) yer alması yasal olarak zorunludur." +privacyPolicy: "Gizlilik Politikası" +privacyPolicyUrl: "Gizlilik Politikası URL'si" +tosAndPrivacyPolicy: "Hizmet Şartları ve Gizlilik Politikası" +avatarDecorations: "Avatar süsleri" +attach: "Ek" +detach: "Kaldır" +detachAll: "Tümünü Kaldır" +angle: "Açı" +flip: "Çevir" +showAvatarDecorations: "Avatar süslerini göster" +releaseToRefresh: "Yenilemek için serbest bırak" +refreshing: "Yenileniyor..." +pullDownToRefresh: "Yenilemek için aşağı çekin" +useGroupedNotifications: "Gruplandırılmış bildirimleri göster" +emailVerificationFailedError: "E-posta adresi doğrulanırken bir sorun oluştu. Bağlantının geçerlilik süresi dolmuş olabilir." +cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı." +doReaction: "Tepki ekle" +code: "Kod" +reloadRequiredToApplySettings: "Ayarları uygulamak için yeniden yükleme gereklidir." +remainingN: "Kalan: {n}" +overwriteContentConfirm: "Mevcut içeriği üzerine yazmak istediğinden emin misin?" +seasonalScreenEffect: "Mevsimsel Ekran Efekti" +decorate: "Süsle" +addMfmFunction: "MFM ekle" +enableQuickAddMfmFunction: "Gelişmiş MFM seçiciyi göster" +bubbleGame: "Kabarcık Oyunu" +sfx: "Ses Efektleri" +soundWillBePlayed: "Ses çalınacaktır" +showReplay: "Tekrarı izle" +replay: "Tekrar oynat" +replaying: "Tekrar gösteriliyor" +endReplay: "Tekrardan çık" +copyReplayData: "Tekrar oynatma verilerini kopyala" +ranking: "Sıralama" +lastNDays: "Son {n} gün" +backToTitle: "Başlığa geri dön" +hemisphere: "Yaşadığınız yer" +withSensitive: "Hassas dosyalara notlar ekle" +userSaysSomethingSensitive: "{name} tarafından gönderilen mesaj hassas içerik barındırmaktadır." +enableHorizontalSwipe: "Kaydırarak sekmeler arasında geçiş yapın" +loading: "Yükleniyor" +surrender: "İptal" +gameRetry: "Tekrar dene" +notUsePleaseLeaveBlank: "Kullanılmıyorsa boş bırakın." +useTotp: "Tek Kullanımlık Şifreyi Girin" +useBackupCode: "Yedek kodları kullanın" +launchApp: "Uygulamayı başlatın" +useNativeUIForVideoAudioPlayer: "Video ve ses oynatımı için tarayıcı kullanıcı arayüzünü kullan" +keepOriginalFilename: "Orijinal dosya adını koru" +keepOriginalFilenameDescription: "Bu ayarı kapatırsan, dosya yüklediğinde dosya adları otomatik olarak rastgele bir dizeyle değiştirilecek." +noDescription: "Açıklama yok" +alwaysConfirmFollow: "Takip ederken her zaman onaylayın" +inquiry: "İletişim" +tryAgain: "Lütfen daha sonra tekrar dene." +confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" +sensitiveMediaRevealConfirm: "Bu hassas bir medya olabilir. Açıklamakta emin misin?" +createdLists: "Oluşturulan listeler" +createdAntennas: "Oluşturulan antenler" +fromX: "{x}'den" +genEmbedCode: "Gömme kodu oluştur" +noteOfThisUser: "Bu kullanıcının notları" +clipNoteLimitExceeded: "Bu klibe daha fazla not eklenemez." +performance: "Başarım" +modified: "Değiştirilmiş" +discard: "At" +thereAreNChanges: "{n} değişiklik var." +signinWithPasskey: "Geçiş Anahtarı ile giriş yapın" +unknownWebAuthnKey: "Bilinmeyen Geçiş Anahtarı" +passkeyVerificationFailed: "Geçiş Anahtarı doğrulama başarısız oldu." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "Geçiş anahtarı doğrulaması başarılı oldu ancak şifresiz oturum açma devre dışıdır." +messageToFollower: "Takipçilere mesaj" +target: "Hedef" +testCaptchaWarning: "Bu işlev CAPTCHA testi amacıyla tasarlanmıştır.\nÜretim ortamında kullanmayın." +prohibitedWordsForNameOfUser: "Kullanıcı adları için yasaklanmış kelimeler" +prohibitedWordsForNameOfUserDescription: "Bu listedeki dizilerden herhangi biri kullanıcının adında yer alıyorsa, ad reddedilecektir. Moderatör ayrıcalıklarına sahip kullanıcılar bu kısıtlamadan etkilenmez." +yourNameContainsProhibitedWords: "Adınız yasaklanmış kelimeler içeriyor" +yourNameContainsProhibitedWordsDescription: "Bu adı kullanmak istiyorsan, lütfen sunucu yöneticinizle iletişime geç." +thisContentsAreMarkedAsSigninRequiredByAuthor: "Yazar tarafından görüntülemek için oturum açma gerektirir." +lockdown: "Karantina" +pleaseSelectAccount: "Hesap seçin" +availableRoles: "Mevcut roller" +acknowledgeNotesAndEnable: "Önlemleri anladıktan sonra açın." +federationSpecified: "Bu sunucu, beyaz liste federasyonunda çalıştırılmaktadır. Yönetici tarafından belirlenen sunucular dışında diğer sunucularla etkileşim kurmak yasaktır." +federationDisabled: "Bu sunucuda federasyon devre dışıdır. Diğer sunuculardaki kullanıcılarla etkileşim kuramazsınız." +draft: "Taslaklar" +confirmOnReact: "Tepki verirken onaylayın" +reactAreYouSure: "“{emoji}” tepkisini eklemek ister misin?" +markAsSensitiveConfirm: "Bu medyayı hassas olarak ayarlamak ister misin?" +unmarkAsSensitiveConfirm: "Bu medya için hassas işaretini kaldırmak ister misin?" +preferences: "Tercihler" +accessibility: "Erişilebilirlik" +preferencesProfile: "Tercihler profili" +copyPreferenceId: "Tercih ID kopyala" +resetToDefaultValue: "Varsayılana dön" +overrideByAccount: "Hesap tarafından geçersiz kılma" +untitled: "İsimsiz" +noName: "İsim yok" +skip: "Atla" +restore: "Geri yükle" +syncBetweenDevices: "Cihazlar arasında senkronizasyon" +preferenceSyncConflictTitle: "Yapılandırılan değer sunucuda mevcuttur." +preferenceSyncConflictText: "Senkronizasyon etkin ayarlar, değerlerini sunucuya kaydeder. Ancak, sunucuda mevcut değerler bulunmaktadır. Hangi değerleri üzerine yazmak istersin?" +preferenceSyncConflictChoiceMerge: "Birleştir" +preferenceSyncConflictChoiceServer: "Sunucuda yapılandırılan değer" +preferenceSyncConflictChoiceDevice: "Cihazda yapılandırılan değer" +preferenceSyncConflictChoiceCancel: "Senkronizasyonu etkinleştirmeyi iptal et" +paste: "Yapıştır" +emojiPalette: "Emoji paleti" +postForm: "Gönderim formu" +textCount: "Karakter sayısı" +information: "Hakkında" +chat: "Sohbet" +directMessage: "Kullanıcıyla sohbet et" +migrateOldSettings: "Eski istemci ayarlarını taşıma" +migrateOldSettings_description: "Bu işlem otomatik olarak yapılmalıdır, ancak herhangi bir nedenle geçiş başarısız olursa, geçiş işlemini manuel olarak kendin başlatabilirsin. Mevcut yapılandırma bilgileri üzerine yazılacaktır." +compress: "Sıkıştır" +right: "Sağ" +bottom: "Alt" +top: "Üst" +embed: "Göm" +settingsMigrating: "Ayarlar taşınıyor, lütfen bir dakika bekle... (Daha sonra Ayarlar→Diğerler→Eski ayarları taşı seçeneğine giderek manuel olarak da taşıyabilirsin)" +readonly: "Sadece okuma" +goToDeck: "Güverteye Dön" +federationJobs: "Federasyon İşleri" +driveAboutTip: "Drive'da, geçmişte yüklediğin dosyaların bir listesi görüntülenir.
\nBu dosyaları notlara eklerken yeniden kullanabilir veya daha sonra paylaşmak üzere önceden yükleyebilirsin.
\nBir dosyayı silerken dikkatli ol, çünkü kullanıldığı her yerde (notlar, sayfalar, avatarlar, afişler vb.) mevcut olmayacakt.
\nAyrıca dosyalarını düzenlemek için klasörler oluşturabilirsin." +scrollToClose: "Kaydırarak kapatın" +advice: "Tavsiye" +realtimeMode: "Gerçek zamanlı mod" +turnItOn: "Aç" +turnItOff: "Kapat" +emojiMute: "Emoji ses kapat" +emojiUnmute: "Emoji ses aç" +muteX: "Sessiz {x}" +unmuteX: "Sesi aç {x}" +abort: "İptal" +tip: "İpucu & Püf Nokta" +redisplayAllTips: "Tüm “İpucu & Püf Nokta” tekrar göster" +hideAllTips: "Tüm “İpucu & Püf Nokta” gizle" +defaultImageCompressionLevel: "Varsayılan görüntü sıkıştırma düzeyi" +defaultImageCompressionLevel_description: "Düşük seviye görüntü kalitesini korur ancak dosya boyutunu artırır.
Yüksek seviye dosya boyutunu azaltır ancak görüntü kalitesini düşürür." +inMinutes: "Dakika(lar)" +inDays: "Gün(ler)" +safeModeEnabled: "Güvenli mod etkinleştirildi" +pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır." +customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor." +themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır." +thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!" +widgets: "Widget'lar" +presets: "Ön ayar" +_imageEditing: + _vars: + filename: "Dosya adı" +_imageFrameEditor: + header: "Başlık" + font: "Yazı tipi" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misin?" +_order: + newest: "Önce yeni" + oldest: "Önce eski" _chat: - home: "Ana sayfa" + noMessagesYet: "Henüz mesaj yok" + newMessage: "Yeni mesaj" + individualChat: "Özel Sohbet" + individualChat_description: "Başka bir kişiyle özel sohbet edin." + roomChat: "Sohbet Odası" + roomChat_description: "Birden fazla kişinin katılabileceği bir sohbet odası.\nÖzel sohbetlere izin vermeyen kişileri de davet edebilirsin, ancak davetini kabul etmeleri gerekir." + createRoom: "Oda Oluştur" + inviteUserToChat: "Kullanıcıları sohbete davet edin" + yourRooms: "Oluşturulan odalar" + joiningRooms: "Katıldığı odalar" + invitations: "Davet" + noInvitations: "Davet yok" + history: "Tarih" + noHistory: "Geçmiş bilgisi mevcut değil" + noRooms: "Oda bulunamadı" + inviteUser: "Kullanıcıları Davet Et" + sentInvitations: "Gönderilen Davetler" + join: "Katıl" + ignore: "Yoksay" + leave: "Odadan çık" + members: "Üyeler" + searchMessages: "Mesajları ara" + home: "Ana Sayfa" + send: "Gönder" + newline: "Yeni satır" + muteThisRoom: "Sessiz oda" + deleteRoom: "Odayı sil" + chatNotAvailableForThisAccountOrServer: "Bu sunucuda veya bu hesapta sohbet özelliği etkin değildir." + chatIsReadOnlyForThisAccountOrServer: "Bu sunucuda veya bu hesapta sohbet okunur modundadır. Yeni mesaj yazamaz veya sohbet odası oluşturamaz/katılamazsınız." + chatNotAvailableInOtherAccount: "Sohbet işlevi diğer kullanıcı için devre dışı bırakılmıştır." + cannotChatWithTheUser: "Bu kullanıcıyla sohbet başlatılamıyor." + cannotChatWithTheUser_description: "Sohbet kullanılamıyor veya karşı taraf sohbeti etkinleştirmedi." + youAreNotAMemberOfThisRoomButInvited: "Bu odanın katılımcısı değilsin, ancak bir davet aldın. Lütfen daveti kabul ederek katıl." + doYouAcceptInvitation: "Daveti kabul ediyor musunuz?" + chatWithThisUser: "Kullanıcıyla sohbet et" + thisUserAllowsChatOnlyFromFollowers: "Bu kullanıcı yalnızca takipçilerinden gelen sohbetleri kabul eder." + thisUserAllowsChatOnlyFromFollowing: "Bu kullanıcı, yalnızca takip ettiği kullanıcılardan gelen sohbetleri kabul eder." + thisUserAllowsChatOnlyFromMutualFollowing: "Bu kullanıcı, yalnızca karşılıklı takip eden kullanıcıların sohbetlerini kabul eder." + thisUserNotAllowedChatAnyone: "Bu kullanıcı kimseyle sohbet etmiyor." + chatAllowedUsers: "Sohbet etmesine izin verilecek kişiler" + chatAllowedUsers_note: "Bu ayardan bağımsız olarak, sohbet mesajı gönderdiğin herkesle sohbet edebilirsin." + _chatAllowedUsers: + everyone: "Herkes" + followers: "Sadece takipçilerin" + following: "Only users you are following" + mutual: "Sadece takiplerin" + none: "Kimse" +_emojiPalette: + palettes: "Palet" + enableSyncBetweenDevicesForPalettes: "Cihazlar arasında palet senkronizasyonunu etkinleştir" + paletteForMain: "Ana palet" + paletteForReaction: "Reaksiyon paleti" +_settings: + driveBanner: "Drive'ı yönetebilir ve yapılandırabilir, kullanımı kontrol edebilir ve dosya yükleme ayarlarını yapılandırabilirsin." + pluginBanner: "Eklentilerle istemci özelliklerini genişletebilirsin. Eklentileri yükleyebilir, ayrı ayrı yapılandırabilir ve yönetebilirsin." + notificationsBanner: "Sunucudan gelen bildirimlerin türlerini ve kapsamını ve push bildirimlerini yapılandırabilirsin." + api: "API" + webhook: "Webhook" + serviceConnection: "Hizmet entegrasyonu" + serviceConnectionBanner: "Dış uygulamalar veya hizmetlerle entegrasyon sağlamak için erişim belirteçlerini ve Webhook'ları yönetin ve yapılandırın." + accountData: "Hesap verileri" + accountDataBanner: "Hesap verilerini yönetmek için dışa ve içe aktarma." + muteAndBlockBanner: "İçeriği gizlemek ve belirli kullanıcıların eylemlerini kısıtlamak için ayarları yapılandırabilir ve yönetebilirsin." + accessibilityBanner: "İstemci, görünüm ve davranışları açısından en iyi şekilde kullanılmak üzere kişiselleştirilebilir ve ayarlanabilir." + privacyBanner: "Hesap gizliliği ile ilgili ayarları, örneğin içerik görünürlüğü, bulunabilirlik ve takip onayı gibi ayarları yapılandırabilirsin." + securityBanner: "Şifre, oturum açma yöntemleri, kimlik doğrulama uygulamaları ve Passkeys gibi hesap güvenliği ile ilgili ayarları yapılandırabilirsin." + preferencesBanner: "İstediğin şekilde istemcinin genel davranışını yapılandırabilirsin." + appearanceBanner: "İstemcinin görünüm ve ekran ayarlarını tercihlerini göre yapılandırabilirsin." + soundsBanner: "İstemcide oynatma için ses ayarlarını yapılandırabilirsin." + timelineAndNote: "Pano ve not" + makeEveryTextElementsSelectable: "Tüm metin öğelerini seçilebilir hale getir" + makeEveryTextElementsSelectable_description: "Bunu etkinleştirmek bazı durumlarda kullanılabilirliği azaltabilir." + useStickyIcons: "Kaydırma sırasında simgeleri takip et" + enableHighQualityImagePlaceholders: "Yüksek kaliteli görüntüler için yer tutucuları göster" + uiAnimations: "UI Animasyonları" + showNavbarSubButtons: "Navigasyon çubuğunda alt düğmeleri göster" + ifOn: "Açıkken" + ifOff: "Kapalıyken" + enableSyncThemesBetweenDevices: "Yüklü temaları cihazlar arasında senkronize edin" + enablePullToRefresh: "Yenilemek için çekin" + enablePullToRefresh_description: "Fareyi kullanırken, kaydırma tekerleğini basılı tutarken sürükle." + realtimeMode_description: "Sunucu ile bağlantı kurar ve içeriği gerçek zamanlı olarak günceller. Bu, trafik ve bellek tüketimini artırabilir." + contentsUpdateFrequency: "İçerik erişim sıklığı" + contentsUpdateFrequency_description: "Değer ne kadar yüksek olursa içerik o kadar sık güncellenir, ancak bu durum performansı düşürür ve trafik ile bellek tüketimini artırır." + contentsUpdateFrequency_description2: "Gerçek zamanlı mod açık olduğunda, bu ayardan bağımsız olarak içerik gerçek zamanlı olarak güncellenir." + showUrlPreview: "URL önizlemesi" + showAvailableReactionsFirstInNote: "Mevcut tepkileri en üstte göster." + showPageTabBarBottom: "Sayfa sekme çubuğunu aşağıda göster" + _chat: + showSenderName: "Gönderenin adını göster" + sendOnEnter: "Enter tuşuna basarak gönderin" +_preferencesProfile: + profileName: "Profil adı" + profileNameDescription: "Bu cihazı tanımlayan bir ad belirle." + profileNameDescription2: "Örnek: “Ana bilgisayar”, “Akıllı telefon”" + manageProfiles: "Profilleri Yönet" +_preferencesBackup: + autoBackup: "Otomatik yedekleme" + restoreFromBackup: "Yedeklemeden geri yükle" + noBackupsFoundTitle: "Yedekleme bulunamadı" + noBackupsFoundDescription: "Otomatik olarak oluşturulan yedekleme bulunamadı, ancak manuel olarak bir yedekleme dosyası kaydettiysen, bunu içe aktarabilir ve geri yükleyebilirsin." + selectBackupToRestore: "Geri yüklemek için bir yedekleme seçin" + youNeedToNameYourProfileToEnableAutoBackup: "Otomatik yedeklemeyi etkinleştirmek için bir profil adı ayarlanmalıdır." + autoPreferencesBackupIsNotEnabledForThisDevice: "Bu cihazda ayarların otomatik yedeklemesi etkinleştirilmemiş." + backupFound: "Ayarların yedeği bulundu" +_accountSettings: + requireSigninToViewContents: "İçeriği görüntülemek için oturum açmanız gerekir." + requireSigninToViewContentsDescription1: "Oluşturduğun tüm notları ve diğer içeriği görüntülemek için oturum açman gerekir. Bu, tarayıcıların bilgilerini toplamasına engel olacaktır." + requireSigninToViewContentsDescription2: "İçerik, URL önizlemelerinde (OGP), web sayfalarına gömülü olarak veya not alıntıları desteklemeyen sunucularda görüntülenmeyecek." + requireSigninToViewContentsDescription3: "Bu kısıtlamalar, diğer uzak sunuculardan gelen birleştirilmiş içerik için geçerli olmayabilir." + makeNotesFollowersOnlyBefore: "Geçmiş notların yalnızca takipçilere gösterilmesini sağlayın" + makeNotesFollowersOnlyBeforeDescription: "Bu özellik etkinleştirildiğinde, yalnızca takipçiler belirlenen tarih ve saatten sonra veya belirlenen süre boyunca görünür olan notları görebilir. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenir." + makeNotesHiddenBefore: "Geçmiş notları gizli yap" + makeNotesHiddenBeforeDescription: "Bu özellik etkinleştirildiğinde, belirlenen tarih ve saatten geçmiş olan veya yalnızca sizin görebildiğiniz notlar. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenecek." + mayNotEffectForFederatedNotes: "Uzak sunucuya bağlı notlar etkilenmeyebilir." + mayNotEffectSomeSituations: "Bu kısıtlamalar basitleştirilmiştir. Uzaktaki bir sunucuda görüntüleme veya moderasyon sırasında gibi bazı durumlarda geçerli olmayabilir." + notesHavePassedSpecifiedPeriod: "Belirtilen sürenin geçtiğini unutmayın." + notesOlderThanSpecifiedDateAndTime: "Belirtilen tarih ve saatten önceki notlar" +_abuseUserReport: + forward: "İleri" + forwardDescription: "Raporu, anonim bir sistem hesabı olarak uzak bir sunucuya iletin." + resolve: "Çözüm" + accept: "Kabul et" + reject: "Reddet" + resolveTutorial: "Raporun içeriği meşruysa, “Kabul Et” seçeneğini seçerek sorunu çözülmüş olarak işaretle.\nRaporun içeriği meşru değilse, “Reddet” seçeneğini seçerek raporu yok say." _delivery: - stop: "Askıya alınmış" + status: "Teslimat durumu" + stop: "Askıya al" + resume: "Teslimat özgeçmişi" _type: none: "Paylaşım" + manuallySuspended: "Manuel olarak askıya alınmış" + goneSuspended: "Sunucu, sunucunun silinmesi nedeniyle askıya alınmıştır." + autoSuspendedForNotResponding: "Sunucu yanıt vermediği için askıya alınmıştır." + softwareSuspended: "Bu yazılım artık dağıtılmadığı için askıya alınmıştır." +_bubbleGame: + howToPlay: "Nasıl oynanır" + hold: "Tut" + _score: + score: "Skor" + scoreYen: "Kazanılan para miktarı" + highScore: "Yüksek puan" + maxChain: "Maksimum zincir sayısı" + yen: "{yen} Yen" + estimatedQty: "{qty} Adet" + scoreSweets: "{onigiriQtyWithUnit} Onigiri" + _howToPlay: + section1: "Konumu ayarlayın ve nesneyi kutuya bırakın." + section2: "Aynı türden iki nesne birbirine dokunduğunda, farklı bir nesneye dönüşür ve puan kazanırsınız." + section3: "Kutu dolduğunda oyun biter. Kutuyu doldurmadan nesneleri birleştirerek yüksek puan almaya çalış!" +_announcement: + forExistingUsers: "Sadece mevcut kullanıcılar" + forExistingUsersDescription: "Bu duyuru, etkinleştirildiğinde yalnızca yayınlandığı anda mevcut olan kullanıcılara gösterilecek. Devre dışı bırakıldığında, yayınlandıktan sonra yeni kaydolan kullanıcılar da bu duyuruyu görecek." + needConfirmationToRead: "Ayrı okuma onayı gerektirir" + needConfirmationToReadDescription: "Etkinleştirildiğinde, bu duyuruyu okundu olarak işaretlemek için ayrı bir onay mesajı görüntülenir. Bu duyuru, “Tümünü okundu olarak işaretle” işlevinden de hariç tutulur." + end: "Arşiv duyurusu" + tooManyActiveAnnouncementDescription: "Çok fazla aktif duyuru olması kullanıcı deneyimini kötüleştirebilir. Artık geçerliliğini yitirmiş duyuruları arşivlemeyi düşün." + readConfirmTitle: "Okundu olarak işaretle?" + readConfirmText: "Bu, “{title}” içeriğini okundu olarak işaretleyecek." + shouldNotBeUsedToPresentPermanentInfo: "Duyuruları, uzun vadede geçerli olacak bilgiler için değil, güncel ve zaman sınırlı bilgileri yayınlamak için kullanmak en iyisidir." + dialogAnnouncementUxWarn: "Aynı anda iki veya daha fazla diyalog tarzı bildirim olması, kullanıcı deneyimini önemli ölçüde etkileyebilir, bu nedenle lütfen bunları dikkatli kullanın." + silence: "Bildirim yok" + silenceDescription: "Bu seçeneği etkinleştirdiğinde, bu duyurunun bildirimi atlanacak ve kullanıcı bunu okumak zorunda kalmayacak." +_initialAccountSetting: + accountCreated: "Hesabınız başarıyla oluşturuldu!" + letsStartAccountSetup: "Şimdi hesabını oluşturalım." + letsFillYourProfile: "Önce profilini oluşturalım." + profileSetting: "Profil ayarları" + privacySetting: "Gizlilik ayarları" + theseSettingsCanEditLater: "Bu ayarları daha sonra istediğin zaman değiştirebilirsin." + youCanEditMoreSettingsInSettingsPageLater: "“Ayarlar” sayfasından yapılandırabileceğin daha birçok ayar bulunmaktadır. Daha sonra mutlaka ziyaret et." + followUsers: "İlgilendiğiniz bazı kullanıcıları takip ederek zaman akışını oluşturmaya çalış." + pushNotificationDescription: "Push bildirimlerini etkinleştirdiğinde, {name} adresinden gelen bildirimleri doğrudan cihazınıza alabilirsin." + initialAccountSettingCompleted: "Profil kurulumu tamamlandı!" + haveFun: "{name} ile iyi eğlenceler!" + youCanContinueTutorial: "{name} (Misskey) öğreticisine geçebilir veya buradan kurulumu sonlandırıp hemen kullanabilirsin." + startTutorial: "Öğreticiye başla" + skipAreYouSure: "Profil kurulumunu cidden atlamak mı istiyorsun?" + laterAreYouSure: "Profil ayarlarını cidden daha sonra mı yapacaksın?" +_initialTutorial: + launchTutorial: "Öğreticiyi izle" + title: "Öğretici" + wellDone: "Tebrikler!" + skipAreYouSure: "Öğreticiyi kapatmak mı istiyorsunuz?" + _landing: + title: "Öğreticiye hoş geldin" + description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." + _note: + title: "Not nedir?" + description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar panoda kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." + reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." + renote: "Bu notu kendi panonda paylaşabilirsin. Ayrıca yorumlarınla birlikte alıntı da yapabilirsin." + reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." + menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." + _reaction: + title: "Reaksiyonlar nedir?" + description: "Notlara çeşitli emojilerle tepki verilebilir. Tepkiler, sadece bir ‘beğeni’ ile ifade edilemeyen nüansları ifade etmeni sağlar." + letsTryReacting: "Notun üzerindeki ‘+’ düğmesine tıklayarak tepkiler eklenebilir. Bu örnek nota tepki verin!" + reactToContinue: "Devam etmek için bir tepki ekle." + reactNotification: "Biri notunuza tepki verdiğinde gerçek zamanlı bildirimler alacaksınız." + reactDone: "“-” düğmesine basarak bir tepkiyi geri alabilirsin." + _timeline: + title: "Pano Kavramı" + description1: "Misskey, kullanıma göre birden fazla Pano sunar (Bazı Pano'lar sunucunun politikalarına bağlı olarak kullanılamayabilir)." + home: "Takip ettiğin hesapların notlarını görüntüleyebilirsin." + local: "Bu sunucudaki tüm kullanıcıların notlarını görüntüleyebilirsin." + social: "Ev ve Yerel Pano'dan notlar görüntülenecek." + global: "Bağlı tüm sunuculardan gelen notları görüntüleyebilirsin." + description2: "Ekranın üst kısmındaki Pano'lar arasında istediğin zaman geçiş yapabilirsin." + description3: "Ayrıca, Liste Pano ve Kanal Pano da bulunmaktadır. Daha fazla ayrıntı için lütfen {link} adresine bakın." + _postNote: + title: "Not Yayınlama Ayarları" + description1: "Misskey'de not yayınlarken çeşitli seçenekler mevcuttur. Yayınlama formu şu şekildedir." + _visibility: + description: "Notunu kimlerin görüntüleyebileceğini sınırlayabilirsin." + public: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." + home: "Yalnızca Ana zaman akışında herkese açık. Profilinizi ziyaret edenler, takipçilerin ve yeniden notlar aracılığıyla bunu görebilirler." + followers: "Sadece takipçiler tarafından görülebilir. Sadece takipçiler görebilir, başkaları göremez ve başkaları tarafından yeniden not edilemez." + direct: "Yalnızca belirli kullanıcılar tarafından görülebilir ve alıcıya bildirim gönderilir. Doğrudan mesajlaşma yerine alternatif olarak kullanılabilir." + doNotSendConfidencialOnDirect1: "Hassas bilgileri gönderirken dikkatli olun!" + doNotSendConfidencialOnDirect2: "Sunucu yöneticileri yazdıklarınızı görebilir. Güvenilir olmayan sunuculardaki kullanıcılara doğrudan not gönderirken hassas bilgilere dikkat edin." + localOnly: "Bu bayrakla yayınlamak, notu diğer sunuculara aktarmaz. Diğer sunuculardaki kullanıcılar, yukarıdaki görüntüleme ayarlarından bağımsız olarak bu notları doğrudan görüntüleyemezler." + _cw: + title: "İçerik Uyarısı" + description: "Gövde yerine, “Yorumlar” alanına yazılan içerik görüntülenecek. “Devamını oku” düğmesine basıldığında gövde görüntülenecek." + _exampleNote: + cw: "Bu kesinlikle sizi acıktıracak!" + note: "Az önce çikolata kaplı bir donut yedim 🍩😋" + useCases: "Bu, sunucu kurallarına uyulurken, gerekli notlar için veya spoiler veya hassas metinlerin kendi kendine kısıtlanması için kullanılır." + _howToMakeAttachmentsSensitive: + title: "Ekleri Hassas Olarak İşaretleme" + description: "Sunucu kuralları gereği gerekli olan veya bozulmaması gereken ekler için “hassas” bayrağı ekle." + tryThisFile: "Bu forma ekli resmi hassas olarak işaretlemeyi dene!" + _exampleNote: + note: "Oops, natto kapağını açarken berbat ettim..." + method: "Bir eki hassas olarak işaretlemek için, dosya küçük resmini tıklayın, menüyü açın ve “Hassas Olarak İşaretle” seçeneğini tıklayın." + sensitiveSucceeded: "Dosya eklerken, lütfen sunucu kurallarına uygun olarak hassasiyet ayarlarını yapın." + doItToContinue: "Devam etmek için ek dosyayı hassas olarak işaretle." + _done: + title: "Eğitimi tamamladınız! 🎉" + description: "Burada tanıtılan işlevler sadece küçük bir kısmıdır. Misskey'i kullanma konusunda daha ayrıntılı bilgi için lütfen şu kaynağa bakın: {link}." +_timelineDescription: + home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin." + local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin." + social: "Pano, Sosyal Pano ve Yerel Pano'dan gelen notları görüntüler." + global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin." +_serverRules: + description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir." +_serverSettings: + iconUrl: "Simge URL'si" + appIconDescription: " {host} bir uygulama olarak görüntülendiğinde kullanılacak simgeyi belirtir." + appIconUsageExample: "Örneğin, PWA olarak veya bir telefonda ana ekran yer imi olarak görüntülendiğinde" + appIconStyleRecommendation: "Simge kare veya daire şeklinde kırpılabileceğinden, içeriğin etrafında renkli kenar boşluğu bulunan bir simge kullanılması önerilir." + appIconResolutionMustBe: "Minimum çözünürlük {resolution}'tür." + manifestJsonOverride: "manifest.json Geçersiz Kılma" + shortName: "Kısa ad" + shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." + fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." + fanoutTimelineDbFallback: "Veritabanına geri dön" + fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek panoların aralığını sınırlar." + reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." + remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" + remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." + remoteNotesCleaningMaxProcessingDuration: "Maksimum temizleme işlem süresi" + remoteNotesCleaningExpiryDaysForEachNotes: "Notları saklamak için minimum gün sayısı" + inquiryUrl: "Sorgu URL'si" + inquiryUrlDescription: "Sorgu formu için sunucu yöneticisine bir URL veya iletişim bilgileri için bir web sayfası belirtin." + openRegistration: "Hesap oluşturmayı açık hale getir" + openRegistrationWarning: "Kayıt açma işlemi riskler içerir. Sunucuyu sürekli olarak izleyen ve herhangi bir sorun durumunda hemen müdahale edebilen bir sistemin varsa, bu işlemi etkinleştirmen önerilir." + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Bir süre boyunca moderatör etkinliği algılanmazsa, spam'ı önlemek için bu ayar otomatik olarak kapatılır." + deliverSuspendedSoftware: "Askıya Alınan Yazılım" + deliverSuspendedSoftwareDescription: "Güvenlik açığı veya diğer nedenlerle sunucunun yazılımının belirli bir isim ve sürüm aralığı için teslimatı durdurabilirsiniz. Bu sürüm bilgileri sunucu tarafından sağlanır ve güvenilirliği garanti edilmez. Sürümü belirtmek için semver aralığı belirtilebilir, ancak >= 2024.3.1 belirtildiğinde 2024.3.1-custom.0 gibi özel sürümler dahil edilmez, bu nedenle >= 2024.3.1-0 gibi ön sürüm belirtimi kullanılması önerilir." + singleUserMode: "Tek kullanıcı modu" + singleUserMode_description: "Bu sunucunun tek kullanıcısıysanız, bu modu etkinleştirerek performansını optimize edebilirsin." + signToActivityPubGet: "ActivityPub GET isteklerini imzalayın" + signToActivityPubGet_description: "Normalde bu özellik etkinleştirilmiş olmalıdır. Bu özelliği devre dışı bırakmak federasyonla ilgili sorunları iyileştirebilir, ancak diğer yandan bazı diğer sunuculara yönelik federasyonu devre dışı bırakabilir." + proxyRemoteFiles: "Proxy uzak dosyalar" + proxyRemoteFiles_description: "Etkinleştirildiğinde, sunucu uzak dosyaları proxy olarak kullanır ve sunar. Bu, resim küçük resimleri oluşturmak ve kullanıcı gizliliğini korumak için kullanışlıdır." + allowExternalApRedirect: "ActivityPub aracılığıyla yapılan sorgular için yönlendirmelere izin ver" + allowExternalApRedirect_description: "Etkinleştirildiğinde, diğer sunucular bu sunucu aracılığıyla üçüncü taraf içeriğini sorgulayabilir, ancak bu durum içerik sahteciliğine yol açabilir." + userGeneratedContentsVisibilityForVisitor: "Kullanıcılar tarafından oluşturulan içeriğin misafirlere görünürlüğü" + userGeneratedContentsVisibilityForVisitor_description: "Bu, uygunsuz ve iyi denetlenmemiş uzaktaki içeriğin kendi sunucunuz aracılığıyla istemeden internette yayınlanmasını önlemek için yararlıdır." + userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler." + restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?" + restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." + entrancePageStyle: "Giriş sayfası stili" + showTimelineForVisitor: "Panoyu göster" + showActivitiesForVisitor: "Aktiviteleri göster" + _userGeneratedContentsVisibilityForVisitor: + all: "Her şey halka açıktır." + localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." + none: "Her şey gizlidir." +_accountMigration: + moveFrom: "Başka bir hesabı bu hesaba taşıyın" + moveFromSub: "Başka bir hesaba takma ad oluşturun" + moveFromLabel: "Orijinal Hesap #{n}" + moveFromDescription: "Bu hesaptan taşınacak hesap için bir takma ad oluşturmalısınız.\nTaşınacak hesabı aşağıdaki biçimde girin: @username@server.example.com\nTakma adı silmek için alanı boş bırakın (önerilmez)." + moveTo: "Bu hesabı başka bir hesaba taşıyın" + moveToLabel: "Taşınacak hesap:" + moveCannotBeUndone: "Hesap taşıma işlemi geri alınamaz." + moveAccountDescription: "Bu işlem, hesabını farklı bir hesaba taşıyacaktır.\n・Bu hesabın takipçileri otomatik olarak yeni hesaba taşınacak.\n・Bu hesap, şu anda takip ettiği tüm kullanıcıları takipten çıkaracak.\n・Bu hesapta yeni notlar vb. oluşturamayacaksın.\n\nTakipçilerin taşınması otomatik olarak gerçekleşirken, takip ettiğin kullanıcıların listesini taşımak için bazı adımları manuel olarak hazırlaman gerekir. Bunu yapmak için, ayarlar menüsünden takipçilerini dışa aktar ve daha sonra yeni hesaba içe aktar. Aynı prosedür, listelerinin yanı sıra sessize aldığın ve engellediğin kullanıcılar için de geçerli.\n\n(Bu açıklama Misskey v13.12.0 ve sonraki sürümler için geçerlidir. Mastodon gibi diğer ActivityPub yazılımları farklı şekilde çalışabilir.)" + moveAccountHowTo: "Geçiş yapmak için, önce taşınacak hesapta bu hesap için bir takma ad oluşturun.\nTakma adı oluşturduktan sonra, taşınacak hesabı aşağıdaki biçimde girin: @username@server.example.com" + startMigration: "Taşın" + migrationConfirm: "Bu hesabı {account} hesabına gerçekten taşımak istiyor musun? Bu işlem başlatıldıktan sonra durdurulamaz veya geri alınamaz ve bu hesabı artık orijinal haliyle kullanamazsın." + movedAndCannotBeUndone: "\nBu hesap taşınmıştır.\nTaşıma işlemi geri alınamaz." + postMigrationNote: "Bu hesap, geçiş işlemi tamamlandıktan 24 saat sonra şu anda takip ettiği tüm hesapları takipten çıkaracak.\nHem takipçi sayısı hem de takip edilenler sayısı sıfır olacak. Takipçilerinin bu hesabın yalnızca takipçilere açık gönderilerini görememesi durumunu önlemek için, takipçilerin bu hesabı takip etmeye devam edecek." + movedTo: "Yeni hesap:" +_achievements: + earnedAt: "Şurada açıldı" + _types: + _notes1: + title: "msky'ımı kuruyorum" + description: "İlk notunuzu yayınlayın" + flavor: "Misskey ile iyi vakit geçirin!" + _notes10: + title: "Bazı notlar" + description: "10 not gönder" + _notes100: + title: "Çok sayıda not" + description: "100 notu yayınla" + _notes500: + title: "Notlarla kaplı" + description: "500 notu yayınla" + _notes1000: + title: "Notlardan oluşan bir dağ" + description: "1.000 not yayınla" + _notes5000: + title: "Taşan notlar" + description: "5.000 not yayınla" + _notes10000: + title: "Süper not" + description: "10.000 not yayınla" + _notes20000: + title: "Daha... fazla... not... lazım..." + description: "20.000 not yayınla" + _notes30000: + title: "Notlar notlar notlar!" + description: "30.000 not yayınla" + _notes40000: + title: "Not fabrikası" + description: "40.000 not yayınla" + _notes50000: + title: "Notların gezegeni" + description: "50.000 not yayınla" + _notes60000: + title: "Not kuasar" + description: "60.000 not yayınla" + _notes70000: + title: "Not kara deliği" + description: "70.000 not yayınla" + _notes80000: + title: "Not galaksisi" + description: "80.000 not yayınla" + _notes90000: + title: "Not evreni" + description: "90.000 not yayınla" + _notes100000: + title: "TÜM NOTLARINIZ BİZE AİTTİR" + description: "100.000 yayınlanmış not" + flavor: "Gerçekten söyleyecek çok şeyin var." + _login3: + title: "Başlangıç I" + description: "Log in for a total of 3 days" + flavor: "Toplam 3 gün boyunca oturum açın" + _login7: + title: "Başlangıç II" + description: "Toplam 7 gün boyunca oturum açın" + flavor: "Henüz işlerin nasıl yürüdüğünü anladığını hissediyor musun?" + _login15: + title: "Başlangıç III" + description: "Toplam 15 gün boyunca oturum açın" + _login30: + title: "Misskist I" + description: "Toplam 30 gün boyunca oturum açın" + _login60: + title: "Misskist II" + description: "Toplam 60 gün boyunca oturum açın" + _login100: + title: "Misskist III" + description: "Toplam 100 gün boyunca oturum açın" + flavor: "Şiddetli Misskist" + _login200: + title: "Düzenli I" + description: "Toplam 200 gün boyunca oturum açın." + _login300: + title: "Düzenli II" + description: "Toplam 300 gün boyunca oturum açın" + _login400: + title: "Düzenli III" + description: "Toplam 400 gün boyunca oturum açın" + _login500: + title: "Uzman I" + description: "Toplam 500 gün boyunca oturum açın" + flavor: "Arkadaşlar, sık sık not almayı sevdiğim söylenir." + _login600: + title: "Uzman II" + description: "Toplam 600 gün boyunca oturum açın" + _login700: + title: "Uzman III" + description: "Toplam 700 gün boyunca oturum açın" + _login800: + title: "Notların Ustası I" + description: "Toplam 800 gün boyunca oturum açın" + _login900: + title: "Notların Ustası II" + description: "Toplam 900 gün boyunca oturum açın" + _login1000: + title: "Notların Ustası III" + description: "Toplam 1.000 gün boyunca oturum açın." + flavor: "Misskey'i kullandığınız için teşekkür ederiz!" + _noteClipped1: + title: "Kesinlikle... kesmeliyim..." + description: "İlk notunu ekle" + _noteFavorited1: + title: "Yıldız gözlemcisi" + description: "İlk notunu favorilerine ekle" + _myNoteFavorited1: + title: "Yıldızları Arayış" + description: "Başka birinin notlarınızdan birini favorilerine eklemesini sağlayın" + _profileFilled: + title: "İyi hazırlanmış" + description: "Profilini oluştur" + _markedAsCat: + title: "Ben bir kediyim." + description: "Hesabını kedi olarak işaretle" + flavor: "Sana daha sonra bir isim vereceğim." + _following1: + title: "İlk kullanıcınızı takip edin" + description: "Bir kullanıcıyı takip et" + _following10: + title: "Devam et... devam et..." + description: "10 kullanıcıyı takip et" + _following50: + title: "Bir sürü arkadaş" + description: "50 hesabı takip et" + _following100: + title: "100 Arkadaş" + description: "100 hesabı takip et" + _following300: + title: "Arkadaş yüklemesi" + description: "300 hesabı takip et" + _followers1: + title: "İlk takipçi" + description: "1 takipçi kazanın" + _followers10: + title: "Beni takip edin!" + description: "10 takipçi kazanın" + _followers50: + title: "Kalabalıklar halinde gelmek" + description: "50 takipçi kazanın" + _followers100: + title: "Popüler" + description: "100 takipçi kazanın" + _followers300: + title: "Lütfen tek sıra halinde dizilin." + description: "300 takipçi kazanın" + _followers500: + title: "Radyo Kulesi" + description: "500 takipçi kazanın" + _followers1000: + title: "Etkileyici" + description: "1.000 takipçi kazanın" + _collectAchievements30: + title: "Başarı Koleksiyoncusu" + description: "30 başarı kazan" + _viewAchievements3min: + title: "Beğeniler Başarılar" + description: "Likes Achievements" + _iLoveMisskey: + title: "Misskey'i seviyorum" + description: "“I ❤ #Misskey” yazısını paylaş" + flavor: "Misskey geliştirme ekibi desteğin için çok teşekkür eder!" + _foundTreasure: + title: "Hazine Avı" + description: "Gizli hazineyi buldunuz." + _client30min: + title: "Kısa mola" + description: "Misskey'i en az 30 dakika açık tutun." + _client60min: + title: "Misskey'de “Miss” yok" + description: "Misskey'i en az 60 dakika açık tutun." + _noteDeletedWithin1min: + title: "Nevermind" + description: "Boş ver" + _postedAtLateNight: + title: "Gececi" + description: "Gece geç saatlerde bir not yayınlayın" + flavor: "Yatma vakti geldi." + _postedAt0min0sec: + title: "Konuşan Saat" + description: "00:00'da bir not yayınlayın." + flavor: "Tık tık tık, güm!" + _selfQuote: + title: "Öz Referans" + description: "Kendi notunuzu alıntı yapın" + _htl20npm: + title: "Akış Panosu" + description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" + _viewInstanceChart: + title: "Analist" + description: "Sunucunun grafiklerini görüntüle" + _outputHelloWorldOnScratchpad: + title: "Merhaba, dünya!" + description: "Scratchpad'de “hello world” yazdırın." + _open3windows: + title: "Çoklu Pencere" + description: "Aynı anda en az 3 pencere açık olsun." + _driveFolderCircularReference: + title: "Döngüsel Referans" + description: "Drive'da yinelemeli olarak iç içe geçmiş bir klasör oluşturmaya çalış." + _reactWithoutRead: + title: "Cidden okudun mu?" + description: "100 karakterden uzun bir notun yayınlanmasından itibaren 3 saniye içinde yanıt verin." + _clickedClickHere: + title: "Buraya tıklayın" + description: "Buraya tıkladınız" + _justPlainLucky: + title: "Sadece Şanslı" + description: "Her 10 saniyede bir %0,005 olasılıkla elde edilme şansı vardır." + _setNameToSyuilo: + title: "Tanrı Kompleksi" + description: "Adınızı “syuilo” olarak ayarlayın." + _passedSinceAccountCreated1: + title: "Birinci Yıl Dönümü" + description: "Hesabınızın oluşturulmasından bu yana bir yıl geçti." + _passedSinceAccountCreated2: + title: "İki Yıllık Yıldönümü" + description: "Hesabınızın oluşturulmasından bu yana iki yıl geçti." + _passedSinceAccountCreated3: + title: "Üçüncü Yıl Dönümü" + description: "Hesabınızın oluşturulmasından bu yana üç yıl geçti." + _loggedInOnBirthday: + title: "Doğum günün kutlu olsun" + description: "Doğum gününüzde giriş yapın" + _loggedInOnNewYearsDay: + title: "Yeni yılınız kutlu olsun!" + description: "Yılın ilk gününde oturum açıldı" + flavor: "Bu sunucuda bir başka harika yıla" + _cookieClicked: + title: "Çerezleri tıklayarak oynanan bir oyun" + description: "Çerezi tıkladı" + flavor: "Wait, are you on the correct website?" + _brainDiver: + title: "Brain Diver" + description: "Brain Diver bağlantısını paylaşın" + flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Test taşması" + description: "Bildirim testini çok kısa bir süre içinde tekrar tekrar tetikle." + _tutorialCompleted: + title: "Misskey Temel Kurs Diploması" + description: "Eğitim tamamlandı" + _bubbleGameExplodingHead: + title: "🤯" + description: "Kabarcık oyunundaki en büyük nesne" + _bubbleGameDoubleExplodingHead: + title: "Çift🤯" + description: "Aynı anda balon oyunundaki en büyük iki nesne" + flavor: "Öğle yemeği kutunu şöyle doldurabilirsin 🤯 🤯 biraz." +_role: + new: "Yeni rol" + edit: "Rolü düzenle" + name: "Rol adı" + description: "Rol tanımı" + permission: "Rol izinleri" + descriptionOfPermission: "Moderators temel moderasyon işlemlerini gerçekleştirebilir.\nAdministrators örneğin tüm ayarlarını değiştirebilir." + assignTarget: "Görev türü" + descriptionOfAssignTarget: "Bu rolün parçası olan ve olmayan kişileri manuel olarak değiştirmek için
manuel
.\nKullanıcıların bir koşula bağlı olarak bu role otomatik olarak atanmasını ve bu rolden çıkarılmasını sağlamak için koşullu." + manual: "Kılavuz" + manualRoles: "Manuel roller" + conditional: "Koşullu" + conditionalRoles: "Koşullu roller" + condition: "Durum" + isConditionalRole: "Bu, koşullu bir roldür." + isPublic: "Kamu rolü" + descriptionOfIsPublic: "Bu rol, atanan kullanıcıların profillerinde görüntülenecek." + options: "Seçenekler" + policies: "Politikalar" + baseRole: "Rol şablonu" + useBaseValue: "Rol şablonu değerini kullan" + chooseRoleToAssign: "Atamak istediğin rolü seç" + iconUrl: "Simge URL'si" + asBadge: "Rozet olarak göster" + descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." + isExplorable: "Rolü keşfedilebilir hale getir" + descriptionOfIsExplorable: "Bu rolün panosu ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." + displayOrder: "Pozisyon" + descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." + preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" + preserveAssignmentOnMoveAccount_description: "Etkinleştirildiğinde, bu rol, bu role sahip bir hesap taşındığında hedef hesaba aktarılacak." + canEditMembersByModerator: "Moderatörlerin bu rol için üye listesini düzenlemesine izin ver" + descriptionOfCanEditMembersByModerator: "Etkinleştirildiğinde, moderatörler ve yöneticiler bu role kullanıcıları atayabilir ve atamalarını kaldırabilir. Devre dışı bırakıldığında, yalnızca yöneticiler kullanıcıları atayabilir." + priority: "Öncelik" + _priority: + low: "Düşük" + middle: "Orta" + high: "Yüksek" + _options: + gtlAvailable: "Global Pano'yu görüntüleyebilir" + ltlAvailable: "Yerel panoyu görüntüleyebilir" + canPublicNote: "Halka açık notlar gönderebilir" + mentionMax: "Bir notta maksimum bahsetme sayısı" + canInvite: "Sunucu davet kodları oluşturabilir" + inviteLimit: "Davet sınırı" + inviteLimitCycle: "Davet sınırı bekleme süresi" + inviteExpirationTime: "Davet süresi dolma aralığı" + canManageCustomEmojis: "Özel emojileri yönetebilir" + canManageAvatarDecorations: "Avatar süslerini yönet" + driveCapacity: "Drive kapasitesi" + maxFileSize: "Yükleyebileceğin maksimum dosya boyutu" + alwaysMarkNsfw: "Dosyaları her zaman NSFW olarak işaretle" + canUpdateBioMedia: "Bir simge veya banner görüntüsünü düzenleyebilir" + pinMax: "Sabitlenmiş notların maksimum sayısı" + antennaMax: "Maksimum anten sayısı" + wordMuteMax: "Kelime sessizlerinde izin verilen maksimum karakter sayısı" + webhookMax: "Maksimum Webhook sayısı" + clipMax: "Maksimum klip sayısı" + noteEachClipsMax: "Bir klip içindeki maksimum not sayısı" + userListMax: "Maksimum kullanıcı listesi sayısı" + userEachUserListsMax: "Kullanıcı listesindeki maksimum kullanıcı sayısı" + rateLimitFactor: "Hız Sınırı" + descriptionOfRateLimitFactor: "Daha düşük oran sınırları daha az kısıtlayıcıdır, daha yüksek olanlar ise daha kısıtlayıcıdır." + canHideAds: "Reklamları gizleyebilir" + canSearchNotes: "Not arama kullanımı" + canSearchUsers: "Kullanıcı arama" + canUseTranslator: "Çevirmen kullanımı" + avatarDecorationLimit: "Maksimum avatar süsü sayısı" + canImportAntennas: "Antenlerin içe aktarılmasına izin ver" + canImportBlocking: "Engellemeyi içe aktarmaya izin ver" + canImportFollowing: "Aşağıdakilerin içe aktarılmasına izin ver" + canImportMuting: "Sessize alma özelliğini içe aktarmaya izin ver" + canImportUserLists: "Listelerin içe aktarılmasına izin ver" + chatAvailability: "Sohbeti İzin Ver" + uploadableFileTypes: "Yüklenebilir dosya türleri" + uploadableFileTypes_caption: "İzin verilen MIME/dosya türlerini belirtir. Birden fazla MIME türü, yeni bir satırla ayırarak belirtilebilir ve joker karakterler yıldız işareti (*) ile belirtilebilir. (örneğin, image/*)" + uploadableFileTypes_caption2: "Bazı dosya türleri algılanamayabilir. Bu tür dosyalara izin vermek için, spesifikasyona {x} ekle." + noteDraftLimit: "Sunucu notlarının olası taslak sayısı" + watermarkAvailable: "Filigran işlevinin kullanılabilirliği" + _condition: + roleAssignedTo: "Manuel rollere atanmış" + isLocal: "Yerel kullanıcı" + isRemote: "Uzak kullanıcı" + isCat: "Kedi Kullanıcıları" + isBot: "Bot Kullanıcıları" + isSuspended: "Askıya alınmış kullanıcı" + isLocked: "Özel hesaplar" + isExplorable: "“Hesabı bulunabilir hale getir” özelliğini etkili bir şekilde kullanan kullanıcı" + createdLessThan: "Hesap oluşturulduktan sonra X'ten az zaman geçti." + createdMoreThan: "Hesap oluşturulmasından bu yana X'ten fazla zaman geçti." + followersLessThanOrEq: "X veya daha az takipçisi var" + followersMoreThanOrEq: "X veya daha fazla takipçisi var" + followingLessThanOrEq: "X veya daha az sayıda hesabı takip ediyor" + followingMoreThanOrEq: "X veya daha fazla hesabı takip ediyor" + notesLessThanOrEq: "Gönderi sayısı şundan az/eşit" + notesMoreThanOrEq: "Gönderi sayısı şundan büyük/eşit" + and: "Koşul-AND" + or: "Koşul-QR" + not: "Koşul-NOT" +_sensitiveMediaDetection: + description: "Makine öğrenimi yoluyla hassas medyayı otomatik olarak tanıyarak sunucu moderasyonunun yükünü azaltır. Bu, sunucu üzerindeki yükü biraz artıracaktır." + sensitivity: "Algılama hassasiyeti" + sensitivityDescription: "Hassasiyeti azaltmak, yanlış algılamaların (yanlış pozitifler) azalmasına neden olurken, hassasiyeti artırmak ise algılamaların kaçırılmasının (yanlış negatifler) azalmasına neden olur." + setSensitiveFlagAutomatically: "Hassas olarak işaretle" + setSensitiveFlagAutomaticallyDescription: "Bu seçenek kapatılsa bile, dahili algılama sonuçları korunacaktır." + analyzeVideos: "Videoların analizini etkinleştir" + analyzeVideosDescription: "Görüntülerin yanı sıra videoları da analiz eder. Bu, sunucu üzerindeki yükü biraz artıracaktır." +_emailUnavailable: + used: "Bu E-Posta adresi zaten kullanılıyor." + format: "Bu E-Posta adresinin biçimi geçersizdir." + disposable: "Tek kullanımlık E-Posta adresleri kullanılamaz." + mx: "Bu E-Posta sunucusu geçersizdir." + smtp: "Bu E-Posta sunucusu yanıt vermiyor." + banned: "Bu E-Posta adresiyle kayıt olamazsınız." +_ffVisibility: + public: "Bu e-posta adresiyle kayıt olamazsınız." + followers: "Sadece takipçiler tarafından görülebilir" + private: "Özel" +_signup: + almostThere: "Neredeyse vardık" + emailAddressInfo: "Lütfen E-Posta adresini gir. Bu adres kamuya açık hale getirilmeyecek." + emailSent: "Onay e-postası E-Posta adresine ({email}) gönderilmiştir. Hesap oluşturma işlemini tamamlamak için e-postadaki bağlantıya tıkla." _accountDelete: - started: "Silme işlemi başlatıldı" + accountDelete: "Hesabı sil" + mayTakeTime: "Hesap silme işlemi kaynak yoğun bir işlem olduğundan, oluşturduğun içerik miktarına ve yüklediğin dosya sayısına bağlı olarak tamamlanması biraz zaman alabilir." + sendEmail: "Hesap silme işlemi tamamlandıktan sonra, bu hesaba kayıtlı E-Posta adresine bir e-posta gönderilecek." + requestAccountDelete: "Hesap silme talebi" + started: "Silme işlemi başlatıldı." + inProgress: "Silme işlemi şu anda devam ediyor." +_ad: + back: "Geri" + reduceFrequencyOfThisAd: "Bu reklamı daha az göster" + hide: "Gizle" + timezoneinfo: "Haftanın günü, sunucunun saat diliminden belirlenir." + adsSettings: "Reklam ayarları" + notesPerOneAd: "Gerçek zamanlı güncelleme reklam yerleşim aralığı (Reklam başına notlar)" + setZeroToDisable: "Bu değeri 0 olarak ayarlayarak gerçek zamanlı güncelleme reklamlarını devre dışı bırakın." + adsTooClose: "Mevcut reklam aralığı çok düşük olduğu için kullanıcı deneyimini önemli ölçüde kötüleştirebilir." +_forgotPassword: + enterEmail: "Kayıt olurken kullandığın E-Posta adresini gir. Şifreni sıfırlayabileceğin bir bağlantı bu adrese gönderilecek." + ifNoEmail: "Kayıt sırasında E-Posta kullanmadıysanız, lütfen bunun yerine sunucu yöneticisiyle iletişime geçin." + contactAdmin: "This instance does not support using email addresses, please contact the instance administrator to reset your password instead." +_gallery: + my: "Benim Galerim" + liked: "Beğenilen Gönderiler" + like: "Beğen" + unlike: "Benzerlerini kaldır" _email: _follow: - title: "seni takip etti" + title: "Yeni bir takipçin var." + _receiveFollowRequest: + title: "Bir takip isteği aldınız." +_plugin: + install: "Eklentileri yükle takip isteği aldınız" + installWarn: "Güvenilir olmayan eklentileri yükleme." + manage: "Eklentileri yönet" + viewSource: "Kaynak görüntüle" + viewLog: "Günlüğü göster" +_preferencesBackups: + list: "Created backups" + saveNew: "Yeni yedeklemeyi kaydet" + loadFile: "Dosyadan yükle" + apply: "Bu cihaza başvur" + save: "Değişiklikleri kaydet" + inputName: "Lütfen bu yedekleme için bir ad girin." + cannotSave: "Kaydetme başarısız oldu" + nameAlreadyExists: "“{name}” adlı bir yedekleme zaten mevcut. Lütfen farklı bir ad girin." + applyConfirm: "Bu cihaza “{name}” yedeklemesini cidden uygulamak istiyor musun? Bu cihazın mevcut ayarları üzerine yazılacaktır." + saveConfirm: "Yedeklemeyi {name} olarak kaydedin?" + deleteConfirm: "{name} yedeklemesini silmek ister misin?" + renameConfirm: "Bu yedeğin adını “{old}” den “{new}” ye değiştirmek ister misin?" + noBackups: "Yedekleme mevcut değil. “Yeni yedekleme oluştur” seçeneğini kullanarak bu sunucudaki istemci ayarlarınızı yedekleyebilirsin." + createdAt: "Oluşturulma tarihi: {date} {time}" + updatedAt: "Güncelleme tarihi: {date} {time}" + cannotLoad: "Yükleme başarısız" + invalidFile: "Geçersiz dosya biçimi" +_registry: + scope: "Kapsam" + key: "Anahtar" + keys: "Anahtarlar" + domain: "Alan adı" + createKey: "Anahtar oluştur" +_aboutMisskey: + about: "Misskey, 2014 yılından beri syuilo tarafından geliştirilen açık kaynaklı bir yazılımdır." + contributors: "Başlıca katkıda bulunanlar" + allContributors: "Tüm katkıda bulunanlar" + source: "Kaynak kodu" + original: "Orijinal" + thisIsModifiedVersion: "{name} orijinal Misskey'in değiştirilmiş bir sürümünü kullanır." + translation: "Misskey'i çevir" + donate: "Misskey'e bağış yapın" + morePatrons: "Burada adı geçmeyen diğer birçok yardımseverin desteğine de teşekkür ederiz. Teşekkürler! 🥰" + patrons: "Müşteriler" + projectMembers: "Proje üyeleri" +_displayOfSensitiveMedia: + respect: "Hassas olarak işaretlenmiş medyayı gizle" + ignore: "Hassas olarak işaretlenmiş medya görüntüleme" + force: "Hide all media" +_instanceTicker: + none: "Asla gösterme" + remote: "Uzak kullanıcılar için göster" + always: "Her zaman göster" +_serverDisconnectedBehavior: + reload: "Otomatik olarak yeniden yükle" + dialog: "Otomatik olarak yeniden yükle" + quiet: "Göze batmayan uyarı göster" +_channel: + create: "Kanal oluştur" + edit: "Kanalı düzenle" + setBanner: "Afiş ayarla" + removeBanner: "Afişi kaldır" + featured: "Trend olan" + owned: "Sahip olunan" + following: "Takip edildi" + usersCount: "{n} Katılımcılar" + notesCount: "{n} Notlar" + nameAndDescription: "Adı ve açıklaması" + nameOnly: "Sadece isim" + allowRenoteToExternal: "Kanal dışında yeniden not alma ve alıntı yapmaya izin ver" +_menuDisplay: + sideFull: "Yan" + sideIcon: "Yan (Simgeler)" + top: "En üst" + hide: "Gizle" +_wordMute: + muteWords: "Sessiz kelimeler" + muteWordsDescription: "AND koşulu için boşluklarla, OR koşulu için satır sonlarıyla ayırın." + muteWordsDescription2: "Surround keywords with slashes to use regular expressions." +_instanceMute: + instanceMuteDescription: "Bu, listelenen sunuculardan gelen tüm notları/yeniden notları sessize alır, sessize alınan bir sunucudan bir kullanıcıya yanıt veren kullanıcıların notları da dahil olmak üzere." + instanceMuteDescription2: "Yeni satırlarla ayırın" + title: "Listelenen sunuculardan notları gizler." + heading: "Sessize alınacak sunucuların listesi" _theme: + explore: "Temaları Keşfedin" + install: "Bir tema yükle" + manage: "Temaları yönet" + code: "Tema kodu" + copyThemeCode: "Tema kodunu kopyala" + description: "Açıklama" + installed: "{name} kuruldu" + installedThemes: "Yüklü temalar" + builtinThemes: "Yerleşik temalar" + instanceTheme: "Sunucu teması" + alreadyInstalled: "Bu tema zaten yüklenmiş." + invalid: "Bu temanın biçimi geçersizdir." + make: "Bir tema oluşturun" + base: "Base" + addConstant: "Sabit ekle" + constant: "Sabit" + defaultValue: "Varsayılan değer" color: "Renk" + refProp: "Bir özelliği referans al" + refConst: "Sabiti referans al" + key: "Anahtar" + func: "İşlevler" + funcKind: "İşlev türü" + argument: "Tartışma" + basedProp: "Referans verilen mülk" + alpha: "Opaklık" + darken: "Koyulaştır" + lighten: "Hafiflet" + inputConstantName: "Bu sabit için bir ad girin" + importInfo: "Buraya tema kodunu girersen, onu tema düzenleyicisine aktarabilirsin." + deleteConstantConfirm: "{const} sabitini cidden silmek istiyor musun?" keys: - mention: "Bahset" - renote: "vazgeçme" + accent: "Aksan" + bg: "Arka plan" + fg: "Metin" + focus: "Odak" + indicator: "Gösterge" + panel: "Panel" + shadow: "Gölge" + header: "Başlık" + navBg: "Kenar çubuğu arka planı" + navFg: "Kenar çubuğu metni" + navActive: "Kenar çubuğu metni (Etkin)" + navIndicator: "Kenar çubuğu göstergesi" + link: "Link" + hashtag: "Hashtag" + mention: "Bahsetmeler" + mentionMe: "Bahsetmeler (Ben)" + renote: "Renote" + modalBg: "Modal arka plan" + divider: "Bölücü" + scrollbarHandle: "Kaydırma çubuğu" + scrollbarHandleHover: "Kaydırma çubuğu (Fareyi üzerine getir)" + dateLabelFg: "Tarih etiketi metni" + infoBg: "Bilgi arka planı" + infoFg: "Bilgi metni" + infoWarnBg: "Uyarı arka planı" + infoWarnFg: "Uyarı metni" + toastBg: "Bildirim arka planı" + toastFg: "Bildirim metni" + buttonBg: "Düğme arka planı" + buttonHoverBg: "Button background (Hover)" + inputBorder: "Giriş alanı kenarlığı" + badge: "Rozet" + messageBg: "Sohbet arka planı" + fgHighlighted: "Vurgulanan Metin" _sfx: - note: "notlar" - notification: "Bildirim" + note: "Yeni not" + noteMy: "Kendi notu" + notification: "Bildirimler" + reaction: "Reaksiyon seçimi hakkında" + chatMessage: "Sohbet Mesajları" +_soundSettings: + driveFile: "Drive'da bir ses dosyası kullanın." + driveFileWarn: "Drive'dan bir ses dosyası seçin." + driveFileTypeWarn: "Bu dosya desteklenmiyor" + driveFileTypeWarnDescription: "Bir ses dosyası seçin" + driveFileDurationWarn: "Ses kaydı çok uzun." + driveFileDurationWarnDescription: "Uzun sesli mesajlar Misskey'in kullanımını engelleyebilir. Devam etmek istiyor musunuz?" + driveFileError: "Ses yüklenemedi. Lütfen ayarları değiştir." +_ago: + future: "Gelecek" + justNow: "Şimdi" + secondsAgo: "{n} sn" + minutesAgo: "{n} dk" + hoursAgo: "{n} sa" + daysAgo: "{n} gün" + weeksAgo: "{n} hafta" + monthsAgo: "{n} ay" + yearsAgo: "{n} yıl" + invalid: "Geçersiz" +_timeIn: + seconds: "{n} saniye içinde" + minutes: "{n} dakika içinde" + hours: "{n} saat içinde" + days: "{n} gün içinde" + weeks: "{n} hafta içinde" + months: "{n} ay içinde" + years: "{n} yıl içinde" +_time: + second: "Saniye(ler)" + minute: "Dakika(lar)" + hour: "Saat(ler)" + day: "Gün(ler)" + month: "Ay" _2fa: - renewTOTPCancel: "Hayır, teşekkürler" + alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin." + registerTOTP: "Kimlik doğrulama uygulamasını kaydet" + step1: "Öncelikle, cihazınıza bir kimlik doğrulama uygulaması (örneğin {a} veya {b}) yükleyin." + step2: "Ardından, bu ekranda görüntülenen QR kodunu tarayın." + step2Uri: "Masaüstü programı kullanıyorsanız aşağıdaki URI'yi girin" + step3Title: "Doğrulama kodunu girin" + step3: "Uygulamanız tarafından sağlanan kimlik doğrulama kodunu (token) girerek kurulumu tamamlayın." + setupCompleted: "Kurulum tamamlandı" + step4: "Bundan sonra, gelecekteki tüm oturum açma girişimlerinde bu tür bir oturum açma jetonu istenecek." + securityKeyNotSupported: "Tarayıcınız güvenlik anahtarlarını desteklemiyor." + registerTOTPBeforeKey: "Güvenlik veya geçiş anahtarını kaydetmek için bir kimlik doğrulama uygulaması kurun." + securityKeyInfo: "Parmak izi veya PIN kimlik doğrulamasının yanı sıra, hesabını daha da güvenli hale getirmek için FIDO2'yi destekleyen donanım güvenlik anahtarları aracılığıyla kimlik doğrulama da ayarlayabilirsin." + registerSecurityKey: "Güvenlik veya geçiş anahtarını kaydedin" + securityKeyName: "Bir anahtar adı girin" + tapSecurityKey: "Güvenlik veya geçiş anahtarını kaydetmek için lütfen tarayıcınızı takip edin." + removeKey: "Güvenlik anahtarını kaldır" + removeKeyConfirm: "{name} anahtarını cidden silmek istiyor musun?" + whyTOTPOnlyRenew: "Güvenlik anahtarı kayıtlı olduğu sürece kimlik doğrulama uygulaması kaldırılamaz." + renewTOTP: "Kimlik doğrulama uygulamasını yeniden yapılandırın" + renewTOTPConfirm: "Bu, önceki uygulamanızdaki doğrulama kodlarının çalışmamasına neden olacaktır." + renewTOTPOk: "Yeniden yapılandır" + renewTOTPCancel: "İptal" + checkBackupCodesBeforeCloseThisWizard: "Bu pencereyi kapatmadan önce, lütfen aşağıdaki yedek kodları not edin." + backupCodes: "Yedek kodlar" + backupCodesDescription: "İki faktörlü kimlik doğrulama uygulamasını kullanamaz hale gelmen durumunda, bu kodları kullanarak hesabınıza erişebilirsin. Her kod yalnızca bir kez kullanılabilir. Lütfen bu kodları güvenli bir yerde sakla." + backupCodeUsedWarning: "Yedek kod kullanıldı. Artık kullanamıyorsanız, lütfen iki faktörlü kimlik doğrulamayı mümkün olan en kısa sürede yeniden yapılandırın." + backupCodesExhaustedWarning: "Tüm yedek kodlar kullanıldı. İki faktörlü kimlik doğrulama uygulamana erişimini kaybedersen, bu hesaba erişemezsin. Lütfen iki faktörlü kimlik doğrulamayı yeniden yapılandır." + moreDetailedGuideHere: "İşte ayrıntılı kılavuz" _permissions: - "read:blocks": "Engellenen hesapları gör" - "write:blocks": "Engellenen hesap listesini düzenle" + "read:account": "Hesap bilgilerini gör" + "write:account": "Hesap bilgilerini düzenle" + "read:blocks": "Engellenen kullanıcıların listesini görüntüle" + "write:blocks": "Engellenen kullanıcılar listeni düzenle" + "read:drive": "Drive dosyalarına ve klasörlerine eriş" + "write:drive": "Drive dosyalarını ve klasörlerini düzenle veya sil" + "read:favorites": "Favoriler listeni görüntüle" + "write:favorites": "Favoriler listeni düzenle" + "read:following": "Takip ettiğin kişilerle ilgili bilgileri görüntüle" + "write:following": "Diğer hesapları takip et veya takipten çıkar" + "read:messaging": "Sohbetlerini görüntüle" + "write:messaging": "Sohbet mesajlarını oluşturun veya silin" + "read:mutes": "Sessize alınan kullanıcıların listesini görüntüle" + "write:mutes": "Sessize alınan kullanıcıların listesini düzenle" + "write:notes": "Notlar oluşturun veya silin" + "read:notifications": "Bildirimlerini görüntüle" + "write:notifications": "Bildirimlerini yönet" + "read:reactions": "Tepkilerini görüntüle" + "write:reactions": "Tepkilerini düzenle" + "write:votes": "Ankete oy verin" + "read:pages": "Sayfalarını görüntüle" + "write:pages": "Sayfalarını düzenle veya sil" + "read:page-likes": "Beğenilen sayfaların listesini görüntüle" + "write:page-likes": "Beğenilen sayfaların listesini düzenle" + "read:user-groups": "Kullanıcı gruplarını görüntüle" + "write:user-groups": "Kullanıcı gruplarını düzenle veya sil" + "read:channels": "Kanallarını görüntüle" + "write:channels": "Kanallarını düzenle" + "read:gallery": "Galeriyi görüntüle" + "write:gallery": "Galeri düzenle" + "read:gallery-likes": "Beğendiğin galeri gönderilerinin listesini görüntüle" + "write:gallery-likes": "Beğendiğin galeri gönderilerinin listesini düzenle" + "read:flash": "Oynat" + "write:flash": "Oyunları Düzenle" + "read:flash-likes": "Beğenilen Oyunların listesini görüntüle" + "write:flash-likes": "Beğenilen Oyunlar listesini düzenle" + "read:admin:abuse-user-reports": "Kullanıcı raporlarını görüntüle" + "write:admin:delete-account": "Kullanıcı hesabını sil" + "write:admin:delete-all-files-of-a-user": "Bir kullanıcının tüm dosyalarını sil" + "read:admin:index-stats": "Veritabanı dizin istatistiklerini görüntüle" + "read:admin:table-stats": "Veritabanı tablosu istatistiklerini görüntüle" + "read:admin:user-ips": "Kullanıcı IP adreslerini görüntüle" + "read:admin:meta": "Sunucu meta verilerini görüntüle" + "write:admin:reset-password": "Kullanıcı şifresini sıfırla" + "write:admin:resolve-abuse-user-report": "Kullanıcı raporunu çözme" + "write:admin:send-email": "E-Posta gönder" + "read:admin:server-info": "Sunucu bilgilerini görüntüle" + "read:admin:show-moderation-log": "Moderasyon günlüğünü görüntüle" + "read:admin:show-user": "Özel kullanıcı bilgilerini görüntüle" + "write:admin:suspend-user": "Kullanıcıyı askıya al" + "write:admin:unset-user-avatar": "Kullanıcı avatarını kaldır" + "write:admin:unset-user-banner": "Kullanıcı afişini kaldır" + "write:admin:unsuspend-user": "Kullanıcı askıya alma işlemini kaldır" + "write:admin:meta": "Sunucu meta verilerini yönetme" + "write:admin:user-note": "Moderasyon notunu yönet" + "write:admin:roles": "Rolleri yönet" + "read:admin:roles": "Rolü görüntüle" + "write:admin:relays": "Röleleri yönetme" + "read:admin:relays": "Röleleri görüntüle" + "write:admin:invite-codes": "Davet kodlarını yönet" + "read:admin:invite-codes": "Davet kodlarını görüntüle" + "write:admin:announcements": "Duyuruları yönet" + "read:admin:announcements": "Duyuruları görüntüle" + "write:admin:avatar-decorations": "Avatar süslerini yönetebilir" + "read:admin:avatar-decorations": "Avatar süslerini görüntüle" + "write:admin:federation": "Federasyon verilerini yönetme" + "write:admin:account": "Kullanıcı hesabını yönet" + "read:admin:account": "Kullanıcı hesabını görüntüle" + "write:admin:emoji": "Emoji'leri yönet" + "read:admin:emoji": "Emojiyi görüntüle" + "write:admin:queue": "İş kuyruğunu yönet" + "read:admin:queue": "İş kuyruğu bilgilerini görüntüle" + "write:admin:promo": "Promosyon notlarını yönet" + "write:admin:drive": "Kullanıcı Drive'ını yönet" + "read:admin:drive": "Kullanıcı Drive bilgilerini görüntüle" + "read:admin:stream": "Yönetici için WebSocket API'sını kullanın" + "write:admin:ad": "Reklamları yönet" + "read:admin:ad": "Reklamları görüntüle" + "write:invite-codes": "Davet kodları oluşturun" + "read:invite-codes": "Davet kodlarını alın" + "write:clip-favorite": "Favorilere eklenen klipleri yönet" + "read:clip-favorite": "Favorilere eklenen klipleri görüntüle" + "read:federation": "Federasyon verilerini alın" + "write:report-abuse": "İhlali bildir" + "write:chat": "Sohbet mesajlarını oluşturun veya silin" + "read:chat": "Sohbeti Gözat" +_auth: + shareAccessTitle: "Uygulama izinlerinin verilmesi" + shareAccess: "“{name}”nin bu hesaba erişmesine izin vermek ister misiniz?" + shareAccessAsk: "Bu uygulamanın hesabınıza erişmesine izin vermek istediğinden emin misin?" + permission: "{name} aşağıdaki izinleri talep etmektedir." + permissionAsk: "Bu uygulama aşağıdaki izinleri talep etmektedir" + pleaseGoBack: "Lütfen uygulamaya geri dönün." + callback: "Uygulamaya geri dönmek" + accepted: "Erişim izni verildi" + denied: "Erişim reddedildi" + scopeUser: "Aşağıdaki kullanıcı olarak çalıştırın" + pleaseLogin: "Uygulamaları yetkilendirmek için lütfen giriş yapın." + byClickingYouWillBeRedirectedToThisUrl: "Erişim izni verildiğinde, otomatik olarak aşağıdaki URL'ye yönlendirileceksin." +_antennaSources: + all: "Tüm notlar" + homeTimeline: "Takip edilen kullanıcıların notları" + users: "Belirli kullanıcılardan gelen notlar" + userList: "Belirtilen kullanıcı listesinden notlar" + userBlacklist: "Bir veya daha fazla belirli kullanıcıya ait olanlar hariç tüm notlar" +_weekday: + sunday: "Pazar" + monday: "Pazartesi" + tuesday: "Salı" + wednesday: "Çarşamba" + thursday: "Perşembe" + friday: "Cuma" + saturday: "Cumartesi" _widgets: profile: "Profil" instanceInfo: "Sunucu Bilgisi" - notifications: "Bildirim" - timeline: "Zaman çizelgesi" + memo: "Yapışkan notlar" + notifications: "Bildirimler" + timeline: "Pano" calendar: "Takvim" + trends: "Trend olan" clock: "Saat" + rss: "RSS okuyucu" + rssTicker: "RSS-Ticker" activity: "Etkinlik" + photos: "Fotoğraflar" + digitalClock: "Dijital saat" + unixClock: "UNIX saati" federation: "Federasyon" - jobQueue: "İşlem sırası" + instanceCloud: "Bulut sunucu" + postForm: "Gönderim formu" + slideshow: "Slayt gösterisi" + button: "Düğme" + onlineUsers: "Çevrimiçi kullanıcılar" + jobQueue: "İş Kuyruğu" + serverMetric: "Sunucu ölçümleri" + aiscript: "AiScript konsolu" + aiscriptApp: "AiScript Uygulaması" + aichan: "Ai" + userList: "Kullanıcı listesi" _userList: - chooseList: "Bir liste seç" + chooseList: "Bir liste seçin" + clicker: "Tıklayıcı" + birthdayFollowings: "Bugünün Doğum Günleri" + chat: "Sohbet" +_widgetOptions: + showHeader: "Başlığı göster" + height: "Yükseklik" + _button: + colored: "Renkli" + _clock: + size: "Boyut" + _birthdayFollowings: + period: "Süre" _cw: - show: "Devamını yükle" + hide: "Gizle" + show: "İçeriği göster" + chars: "{count} karakter" + files: "{count} dosya(lar)" _poll: - vote: "Oy kullan" + noOnlyOneChoice: "En az iki seçenek gereklidir." + choiceN: "Seçim {n}" + noMore: "Daha fazla seçenek ekleyemezsin." + canMultipleVote: "Birden fazla seçenek seçilmesine izin ver" + expiration: "Anketi sonlandır" + infinite: "Asla" + at: "Şurada bitir..." + after: "Sonrasında bitir..." + deadlineDate: "Bitiş tarihi" + deadlineTime: "Zaman" + duration: "Süre" + votesCount: "{n} oy" + totalVotes: "Toplam {n} oy" + vote: "Oy ver" + showResult: "Sonuçları görüntüle" + voted: "Oylandı" + closed: "Sona erdi" + remainingDays: "{d} gün {h} saat kaldı" + remainingHours: "{h} saat {m} dakika kaldı" + remainingMinutes: "{m} dakika {s} saniye kaldı" + remainingSeconds: "{s} saniye kaldı" _visibility: - publicDescription: "Herkese açık" - home: "Ana sayfa" - followers: "takipçi" + public: "Halka açık" + publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." + home: "Pano" + homeDescription: "Yalnızca ana panoya gönder" + followers: "Takipçiler" + followersDescription: "Sadece takipçilerine görünür hale getir" + specified: "Doğrudan" + specifiedDescription: "Yalnızca belirli kullanıcılar için görünür hale getir" + disableFederation: "Federasyon olmadan" + disableFederationDescription: "Diğer sunuculara aktarma" +_postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Yüklenmemiş dosyalar var, bunları silip formu kapatmak ister misin?" + uploaderTip: "Dosya henüz yüklenmemiş. Dosya menüsünden dosyayı yeniden adlandırabilir, görüntüleri kırpabilir, filigran ekleyebilir ve dosyayı sıkıştırabilir veya sıkıştırmayı kaldırabilirsin. Notu yayınladığında dosyalar otomatik olarak yüklenir." + replyPlaceholder: "Bu notu yanıtla..." + quotePlaceholder: "Bu notu alıntı yap..." + channelPlaceholder: "Bir kanala gönder..." + _howToUse: + visibility_title: "Görünürlük" + menu_title: "Menü" + _placeholders: + a: "Ne yapıyorsun?" + b: "Çevrende neler oluyor?" + c: "Aklında ne var?" + d: "Ne söylemek istiyorsun?" + e: "Yazmaya başlayın..." + f: "Yazmanızı bekliyoruz..." _profile: - username: "Kullanıcı Adı" + name: "Ad" + username: "Kullanıcı adı" + description: "Biyografi" + youCanIncludeHashtags: "Biyografinize hashtag'ler de ekleyebilirsiniz." + metadata: "Ek Bilgiler" + metadataEdit: "Ek bilgileri düzenle" + metadataDescription: "Bunları kullanarak profilinde ek bilgi alanları görüntüleyebilirsin." + metadataLabel: "Etiket" + metadataContent: "İçerik" + changeAvatar: "Avatar değiştir" + changeBanner: "Banner değiştir" + verifiedLinkDescription: "Buraya profiline bağlantı içeren bir URL girerek, alanın yanında bir sahiplik doğrulama simgesi görüntülenebilir." + avatarDecorationMax: "En fazla {max} süs ekleyebilirsin." + followedMessage: "Takip edildiğinizde gönderilen mesaj" + followedMessageDescription: "Abonelerin seni takip ettiklerinde görüntülenmesini istediğin kısa bir mesaj ayarlayabilirsin." + followedMessageDescriptionForLockedAccount: "Takip isteklerinin onay gerektirmesini ayarladıysan, bir takip isteğini kabul ettiğinde bu mesaj görüntülenir." _exportOrImport: - followingList: "takipçi" - muteList: "Gizle" - blockingList: "engelle" - userLists: "Listeler" + allNotes: "Tüm notlar" + favoritedNotes: "Favori notlar" + clips: "Klip" + followingList: "Takip edilen kullanıcılar" + muteList: "Sessize alınan kullanıcılar" + blockingList: "Engellenen kullanıcılar" + userLists: "Kullanıcı listeleri" + excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut" + excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut" + withReplies: "İçe aktarılan kullanıcıların yanıtlarını panoya dahil edin" _charts: federation: "Federasyon" + apRequest: "Talepler" + usersIncDec: "Kullanıcı sayısındaki fark" + usersTotal: "Toplam kullanıcı sayısı" + activeUsers: "Aktif kullanıcılar" + notesIncDec: "Not sayısındaki fark" + localNotesIncDec: "Yerel notaların sayısındaki fark" + remoteNotesIncDec: "Uzak notların sayısındaki fark" + notesTotal: "Toplam not sayısı" + filesIncDec: "Dosya sayısındaki fark" + filesTotal: "Toplam dosya sayısı" + storageUsageIncDec: "Depolama kullanımı farkı" + storageUsageTotal: "Total storage usage" +_instanceCharts: + requests: "Talepler" + users: "Kullanıcı sayısındaki fark" + usersTotal: "Toplam kullanıcı sayısı" + notes: "Not sayısındaki fark" + notesTotal: "Toplam not sayısı" + ff: "Takip / Takipçi sayısı farkı" + ffTotal: "Takip / Takipçi toplam sayısı" + cacheSize: "Önbellek boyutundaki fark" + cacheSizeTotal: "Önbelleğin toplam boyutu" + files: "Dosya sayısındaki fark" + filesTotal: "Toplam dosya sayısı" _timelines: - home: "Ana sayfa" - global: "Küresel" + home: "Pano" + local: "Yerel" + social: "Sosyal" + global: "Global" +_play: + new: "Oyun Oluştur" + edit: "Düzenle Oynat" + created: "Oyun oluşturuldu" + updated: "Düzenlenmiş oynat" + deleted: "Oyun silindi" + pageSetting: "Oyun ayarları" + editThisPage: "Bu Oyunu Düzenle" + viewSource: "Kaynak görüntüle" + my: "Oyunlarım" + liked: "Beğenilen Oyunlar" + featured: "Popüler" + title: "Başlık" + script: "Senaryo" + summary: "Açıklama" + visibilityDescription: "Özel olarak ayarlamak, profilinde görünmeyeceği anlamına gelir, ancak URL'ye sahip olan herkes yine de erişebilir." _pages: + newPage: "Yeni bir Sayfa oluşturun" + editPage: "Bu sayfayı düzenle" + readPage: "Bu Sayfanın Kaynağını Görüntüleme" + pageSetting: "Sayfa ayarları" + nameAlreadyExists: "Belirtilen Sayfa URL'si zaten mevcut." + invalidNameTitle: "Belirtilen Sayfa URL geçersiz" + invalidNameText: "Sayfa başlığının boş olmadığından emin olun." + editThisPage: "Bu sayfayı düzenle" + viewSource: "Kaynak görüntüle" + viewPage: "Sayfalarını görüntüle" + like: "Beğen" + unlike: "Benzerlerini kaldır" + my: "Benzerlerini kaldır" + liked: "Beğenilen Sayfalar" + featured: "Popüler" + inspector: "Müfettiş" + contents: "İçindekiler" + content: "Sayfa bloğu" + variables: "Değişkenler" + title: "Başlık" + url: "Sayfa URL'si" + summary: "Sayfa özeti" + alignCenter: "Merkez öğeleri" + hideTitleWhenPinned: "Profiline sabitlendiğinde sayfa başlığını gizle" + font: "Yazı tipi" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + eyeCatchingImageSet: "Küçük resmi ayarla" + eyeCatchingImageRemove: "Küçük resmi sil" + chooseBlock: "Blok ekle" + enterSectionTitle: "Bölüm başlığını girin" + selectType: "Bir tür seçin" + contentBlocks: "İçerik" + inputBlocks: "Giriş" + specialBlocks: "Özel" blocks: + text: "Metin" + textarea: "Metin alanı" + section: "Bölüm" image: "Görseller" + button: "Düğme" + dynamic: "Dinamik Bloklar" + dynamicDescription: "Bu blok kaldırılmıştır. Bundan sonra lütfen {play} kullanın." + note: "Gömülü not" + _note: + id: "Not Kimliği" + idDescription: "Alternatif olarak notun URL buraya yapıştırabilirsin." + detailed: "Ayrıntılı görünüm" +_relayStatus: + requesting: "Beklemede" + accepted: "Accepted" + rejected: "Reddedildi" _notification: + fileUploaded: "Dosya başarıyla yüklendi" + youGotMention: "{name} sizden bahsetti." + youGotReply: "{name} size yanıt verdi" + youGotQuote: "{name} sizden alıntı yaptı" + youRenoted: "{name}'den Renote" youWereFollowed: "seni takip etti" + youReceivedFollowRequest: "Bir takip isteği aldınız." + yourFollowRequestAccepted: "Takip isteğin kabul edildi." + pollEnded: "Anket sonuçları açıklandı." + newNote: "Yeni not" unreadAntennaNote: "{name} anteni" + roleAssigned: "Verilen rol" + chatRoomInvitationReceived: "Sohbet odasına davet edildin." + emptyPushNotificationMessage: "Push bildirimleri güncellendi" + achievementEarned: "Achievement unlocked" + testNotification: "Test bildirimi" + checkNotificationBehavior: "Bildirim görünümünü kontrol edin" + sendTestNotification: "Test bildirimi gönder" + notificationWillBeDisplayedLikeThis: "Bildirimler şöyle görünür" + reactedBySomeUsers: "{n} kullanıcı tepki gösterdi" + likedBySomeUsers: "{n} kullanıcı notunuzu beğendi." + renotedBySomeUsers: "{n} kullanıcıdan gelen hatırlatma" + followedBySomeUsers: "{n} kullanıcı tarafından takip ediliyor" + flushNotification: "Bildirimleri temizle" + exportOfXCompleted: "{x} ihracatı tamamlandı." + login: "Biri oturum açtı" + createToken: "Bir erişim jetonu oluşturuldu." + createTokenDescription: "Eğer bilmiyorsanız, “{text}” aracılığıyla erişim jetonunu silin." _types: - follow: "takipçi" - mention: "Bahset" - renote: "vazgeçme" - quote: "alıntı" - reaction: "Tepkiler" - receiveFollowRequest: "Takip isteği alındı" - followRequestAccepted: "Takip isteği kabul edildi" - login: "Giriş Yap " + all: "Tümü" + note: "Yeni notlar" + follow: "Yeni takipçiler" + mention: "Bahsetmeler" + reply: "Yanıtlar" + renote: "Renote" + quote: "Alıntılar" + reaction: "Tepki" + pollEnded: "Anketler sona eriyor" + receiveFollowRequest: "Takip istekleri alındı" + followRequestAccepted: "Kabul edilen takip istekleri" + roleAssigned: "Verilen rol" + chatRoomInvitationReceived: "Sohbet odasına davet edildi" + achievementEarned: "Başarı kilidi açıldı" + exportCompleted: "İhracat işlemi tamamlandı." + login: "Oturum Aç" + createToken: "Erişim jetonu oluştur" + test: "Bildirim testi" + app: "Bağlı uygulamalardan gelen bildirimler" _actions: - reply: "yanıt" - renote: "vazgeçme" + followBack: "seni takip ettim" + reply: "Yanıtla" + renote: "Renote" _deck: - configureColumn: "Sütun seçenekleri" + alwaysShowMainColumn: "Ana sütunu her zaman göster" + columnAlign: "Sütunları hizala" + columnGap: "Sütunlar arasındaki kenar boşluğu" + deckMenuPosition: "Sütunlar arasındaki kenar boşluğu" + navbarPosition: "Gezinti çubuğu konumu" + addColumn: "Sütun ekle" + newNoteNotificationSettings: "Notification setting for new notes" + configureColumn: "Sütun ayarları" + swapLeft: "Sol sütunla değiştir" + swapRight: "Sağ sütunla değiştir" + swapUp: "Yukarıdaki sütunla değiştir" + swapDown: "Aşağıdaki sütunla değiştir" + stackLeft: "Sol sütunda yığın" + popRight: "Sağdaki pop sütunu" + profile: "Profil" + newProfile: "Yeni profil" + deleteProfile: "Profili sil" + introduction: "Sütunları serbestçe düzenleyerek size en uygun arayüzü oluşturun!" + introduction2: "Ekranın sağındaki + işaretine tıklayarak istediğin zaman yeni sütunlar ekleyebilirsin." + widgetsIntroduction: "Lütfen sütun menüsünden “Widget'ları düzenle” seçeneğini seç ve bir widget ekle." + useSimpleUiForNonRootPages: "Gezinilen sayfalar için basit kullanıcı arayüzü kullanın" + usedAsMinWidthWhenFlexible: "“Otomatik genişlik ayarı” seçeneği etkinleştirildiğinde, bunun için minimum genişlik kullanılacak." + flexible: "Otomatik genişlik ayarı" + enableSyncBetweenDevicesForProfiles: "Cihazlar arasında profil bilgilerinin senkronizasyonunu etkinleştir" _columns: - notifications: "Bildirim" - tl: "Zaman çizelgesi" - list: "Listeler" + main: "Ana" + widgets: "Widget'lar" + notifications: "Bildirimler" + tl: "Pano" + antenna: "Antenler" + list: "Liste" + channel: "Kanal" mentions: "Bahsetmeler" + direct: "Doğrudan notlar" + roleTimeline: "Rol Pano" + chat: "Sohbet" +_dialog: + charactersExceeded: "Maksimum karakter sınırını aştınız! Şu anda {current} karakterde {max} karakterlik sınırın {current} karakterinde bulunuyorsunuz." + charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}." +_disabledTimeline: + title: "Pano devre dışı bırakıldı" + description: "Mevcut rollerinle bu Pano kullanılamaz." +_drivecleaner: + orderBySizeDesc: "Azalan Dosya Boyutları" + orderByCreatedAtAsc: "Yükselen Tarihler" +_webhookSettings: + createWebhook: "Webhook oluştur" + modifyWebhook: "Webhook'u değiştir" + name: "Webhook'u değiştir" + secret: "Gizli" + trigger: "Tetikleyici" + active: "Etkin" + _events: + follow: "Bir kullanıcıyı takip ederken" + followed: "Takip edildiğinde" + note: "Not gönderirken" + reply: "Yanıt alındığında" + renote: "Yeniden not edildiğinde" + reaction: "Tepki aldığınızda" + mention: "Bahsedildiğinde" + _systemEvents: + abuseReport: "Yeni bir rapor alındığında" + abuseReportResolved: "Çözüldüğünde rapor" + userCreated: "Kullanıcı oluşturulduğunda" + inactiveModeratorsWarning: "Moderatörler bir süredir aktif olmadıklarında" + inactiveModeratorsInvitationOnlyChanged: "Bir moderatör bir süre aktif olmadığında ve sunucu davetle erişilebilir hale getirildiğinde" + deleteConfirm: "Webhook'u silmek istediğinden emin misin?" + testRemarks: "Anahtarın sağındaki düğmeyi tıklayarak sahte verilerle bir test Webhook gönderin." +_abuseReport: + _notificationRecipient: + createRecipient: "Raporlar için alıcı ekle" + modifyRecipient: "Raporlar için alıcıyı düzenle" + recipientType: "Bildirim türü" + _recipientType: + mail: "E-Posta" + webhook: "Webhook" + _captions: + mail: "Raporları aldığınızda, E-Postayı moderatörlerin e-posta adreslerine gönderin." + webhook: "Raporları aldığınızda veya çözdüğünüzde Sistem Webhook'una bir bildirim gönderin." + keywords: "Anahtar kelimeler" + notifiedUser: "Bildirilecek kullanıcılar" + notifiedWebhook: "Kullanılacak webhook" + deleteConfirm: "Bildirim alıcısını silmek istediğinden emin misin?" _moderationLogTypes: - suspend: "askıya al" - resetPassword: "Şifre sıfırlama" + createRole: "Rol oluşturuldu" + deleteRole: "Rol silindi" + updateRole: "Rol güncellendi" + assignRole: "Rol atandı" + unassignRole: "Görevinden alınmış" + suspend: "Askıya alınmış" + unsuspend: "Askıya alınmamış" + addCustomEmoji: "Özel emoji eklendi" + updateCustomEmoji: "Özel emoji güncellendi" + deleteCustomEmoji: "Özel emoji silindi" + updateServerSettings: "Sunucu ayarları güncellendi" + updateUserNote: "Moderasyon notu güncellendi" + deleteDriveFile: "Dosya silindi" + deleteNote: "Not silindi" + createGlobalAnnouncement: "Global duyuru oluşturuldu" + createUserAnnouncement: "Kullanıcı duyurusu oluşturuldu" + updateGlobalAnnouncement: "Global duyuru güncellendi" + updateUserAnnouncement: "Kullanıcı duyurusu güncellendi" + deleteGlobalAnnouncement: "Global duyuru silindi" + deleteUserAnnouncement: "Kullanıcı duyurusu silindi" + resetPassword: "Şifreyi sıfırla" + suspendRemoteInstance: "Uzak sunucu askıya alındı" + unsuspendRemoteInstance: "Uzak sunucu askıya alınmadı" + updateRemoteInstanceNote: "Uzak sunucular için güncellenmiş moderasyon notu" + markSensitiveDriveFile: "Hassas olarak işaretlenmiş dosya" + unmarkSensitiveDriveFile: "Dosya hassas olarak işaretlenmemiş" + resolveAbuseReport: "Rapor çözüldü" + forwardAbuseReport: "Rapor iletildi" + updateAbuseReportNote: "Güncellenen raporun moderasyon notu" + createInvitation: "Davet oluşturuldu" + createAd: "Reklam oluşturuldu" + deleteAd: "Reklam silindi" + updateAd: "Reklam güncellendi" + createAvatarDecoration: "Avatar dekorasyonu oluşturuldu" + updateAvatarDecoration: "Avatar dekorasyonu güncellendi" + deleteAvatarDecoration: "Avatar süsü silindi" + unsetUserAvatar: "Kullanıcı avatarı ayarlanmamış" + unsetUserBanner: "Kullanıcı başlığı ayarlanmamış" + createSystemWebhook: "Sistem Webhook oluşturuldu" + updateSystemWebhook: "Sistem Webhook güncellendi" + deleteSystemWebhook: "Sistem Webhook silindi" + createAbuseReportNotificationRecipient: "Oluşturulan raporlar için alıcı" + updateAbuseReportNotificationRecipient: "Raporlar için alıcı güncellendi" + deleteAbuseReportNotificationRecipient: "Silinen raporlar için alıcı" + deleteAccount: "Hesap silindi" + deletePage: "Sayfa silindi" + deleteFlash: "Oyun silindi" + deleteGalleryPost: "Galeri gönderisi silindi" + deleteChatRoom: "Deleted Chat Room" + updateProxyAccountDescription: "Proxy hesabının açıklamasını güncelle" +_fileViewer: + title: "Dosya ayrıntıları" + type: "Dosya türü" + size: "Dosya boyutu" + url: "URL" + uploadedAt: "Yüklendiği tarih" + attachedNotes: "Ekli notlar" + usage: "Kullanılmış" + thisPageCanBeSeenFromTheAuthor: "Bu sayfa, bu dosyayı yükleyen kullanıcı tarafından görülebilir." +_externalResourceInstaller: + title: "Harici siteden yükle" + checkVendorBeforeInstall: "Yüklemeden önce bu kaynağın dağıtımcısının güvenilir olduğundan emin olun." + _plugin: + title: "Bu eklentiyi yüklemek ister misin?" + _theme: + title: "Bu temayı yüklemek ister misin?" + _meta: + base: "Temel renk şeması" + _vendorInfo: + title: "Dağıtıcı bilgileri" + endpoint: "Referans uç nokta" + hashVerify: "Hash doğrulama" + _errors: + _invalidParams: + title: "Geçersiz parametreler" + description: "Harici bir siteden veri yüklemek için yeterli bilgi yok. Lütfen girdiğin URL'yi kontrol et." + _resourceTypeNotSupported: + title: "Bu harici kaynak desteklenmemektedir." + description: "Bu harici kaynağın türü desteklenmemektedir. Lütfen site yöneticisiyle iletişime geç." + _failedToFetch: + title: "Veriler alınamadı" + fetchErrorDescription: "Harici siteyle iletişim sırasında bir hata oluştu. Tekrar denemen sorunu çözmezse, lütfen site yöneticisine başvur." + parseErrorDescription: "Harici siteden yüklenen veriler işlenirken bir hata oluştu. Lütfen site yöneticisiyle iletişime geçin." + _hashUnmatched: + title: "Veri doğrulama başarısız oldu" + description: "Alınan verilerin bütünlüğünü doğrularken bir hata oluştu. Güvenlik önlemi olarak, kurulum devam edemez. Lütfen site yöneticisiyle iletişime geçin." + _pluginParseFailed: + title: "AiScript Hatası" + description: "İstenen veriler başarıyla alındı, ancak AiScript ayrıştırma sırasında bir hata oluştu. Lütfen eklenti yazarına başvurun. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _pluginInstallFailed: + title: "Eklenti kurulumu başarısız oldu" + description: "Eklenti yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _themeParseFailed: + title: "Tema ayrıştırma başarısız oldu" + description: "İstenen veriler başarıyla alındı, ancak tema ayrıştırma sırasında bir hata oluştu. Lütfen tema yazarıyla iletişime geçin. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _themeInstallFailed: + title: "Tema yüklenemedi" + description: "Tema yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." +_dataSaver: + _media: + title: "Medya yükleniyor" + description: "Görüntülerin/videoların otomatik olarak yüklenmesini engeller. Gizli görüntüler/videolar dokunulduğunda yüklenir." + _avatar: + title: "Avatar resmi" + description: "Avatar görüntüsünün animasyonunu durdurun. Animasyonlu görüntüler normal görüntülere göre dosya boyutu açısından daha büyük olabilir ve bu da veri trafiğinde daha fazla azalmaya yol açabilir." + _urlPreviewThumbnail: + title: "URL önizleme küçük resimlerini gizle" + description: "URL önizleme küçük resimleri artık yüklenmeyecek." + _disableUrlPreview: + title: "URL önizlemesini devre dışı bırak" + description: "URL önizleme işlevini devre dışı bırakır. Küçük resimler aksine, bu işlev bağlantılı bilgilerin kendisinin yüklenmesini azaltır." + _code: + title: "Kod vurgulama" + description: "MFM vb. programlarda kod vurgulama notasyonları kullanılıyorsa, bunlar dokunulana kadar yüklenmez. Sözdizimi vurgulama, her programlama dili için vurgu tanım dosyalarının indirilmesini gerektirir. Bu nedenle, bu dosyaların otomatik olarak yüklenmesinin devre dışı bırakılması, iletişim verisi miktarını azaltması beklenir." +_hemisphere: + N: "Kuzey Yarımküre" + S: "Güney Yarımküre" + caption: "Bazı istemci ayarlarında mevsimi belirlemek için kullanılır." +_reversi: + reversi: "Tersine çevirme" + gameSettings: "Oyun ayarları" + chooseBoard: "Bir tahta seçin" + blackOrWhite: "Siyah/Beyaz" + blackIs: "{name} siyah oynuyor." + rules: "Kurallar" + thisGameIsStartedSoon: "Oyun kısa süre içinde başlayacak." + waitingForOther: "Rakibin sırasını bekle" + waitingForMe: "Sıranı bekliyorsun" + waitingBoth: "Hazır olun" + ready: "Hazır" + cancelReady: "Hazır değil" + opponentTurn: "Rakibin sırası" + myTurn: "Sıra sende" + turnOf: "Sıra {name}'de." + pastTurnOf: "{name}'nin sırası" + surrender: "Teslimiyet" + surrendered: "Teslim oldu" + timeout: "Zaman doldu" + drawn: "Çiz" + won: "{name} kazandı" + black: "Siyah" + white: "Beyaz" + total: "Toplam" + turnCount: "{count} döndür" + myGames: "Benim turlarım" + allGames: "Tüm turlar" + ended: "Sona erdi" + playing: "Şu anda oynatılıyor" + isLlotheo: "Taş sayısı daha az olan kazanır (Llotheo)" + loopedMap: "Döngüsel harita" + canPutEverywhere: "Fayanslar her yere yerleştirilebilir." + timeLimitForEachTurn: "Sıra için zaman sınırı" + freeMatch: "Ücretsiz Eşleştirme" + lookingForPlayer: "Rakip aranıyor..." + gameCanceled: "Oyun iptal edildi." + shareToTlTheGameWhenStart: "Oyun başlatıldığında panoda paylaş" + iStartedAGame: "Oyun başladı! #MisskeyReversi" + opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." + allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" + disallowIrregularRules: "Düzensiz kurallar yok" + showBoardLabels: "Tahtada satır ve sütun numaralarını göster" + useAvatarAsStone: "Taşları kullanıcı avatarlarına dönüştürün" +_offlineScreen: + title: "Çevrimdışı - sunucuya bağlanılamıyor" + header: "Sunucuya bağlanılamıyor" +_urlPreviewSetting: + title: "URL önizleme ayarları" + enable: "URL önizlemesini etkinleştir" + allowRedirect: "URL önizleme yönlendirmesine izin ver" + allowRedirectDescription: "Bir URL'de yönlendirme ayarlanmışsa, bu özelliği etkinleştirerek yönlendirmeyi takip edebilir ve yönlendirilen içeriğin önizlemesini görüntüleyebilirsin. Bu özelliği devre dışı bırakmak sunucu kaynaklarından tasarruf sağlar, ancak yönlendirilen içerik görüntülenmez." + timeout: "Önizleme alırken zaman aşımı (ms)" + timeoutDescription: "Önizlemeyi almak bu değerden daha uzun sürerse, önizleme oluşturulmaz." + maximumContentLength: "Maksimum İçerik Uzunluğu (bayt)" + maximumContentLengthDescription: "Content-Length bu değerden yüksekse, önizleme oluşturulmaz." + requireContentLength: "Yalnızca Content-Length değerini alabiliyorsanız önizlemeyi oluşturun." + requireContentLengthDescription: "Diğer sunucu Content-Length değerini döndürmezse, önizleme oluşturulmaz." + userAgent: "Kullanıcı Aracısı" + userAgentDescription: "Önizlemeleri alırken kullanılacak Kullanıcı Aracısını ayarlar. Boş bırakılırsa, varsayılan Kullanıcı Aracısı kullanılır." + summaryProxy: "Önizlemeler oluşturan proxy uç noktaları" + summaryProxyDescription: "Misskey'in kendisi değil, Summaly Proxy kullanarak önizlemeler oluştur." + summaryProxyDescription2: "Aşağıdaki parametreler, sorgu dizesi olarak proxy'ye bağlanır. Proxy bunları desteklemiyorsa, değerler yok sayılır." +_mediaControls: + pip: "Resim içinde resim" + playbackRate: "Oynatma Hızı" + loop: "Döngüsel oynatma" +_contextMenu: + title: "Bağlam menüsü" + app: "Uygulama" + appWithShift: "Shift tuşuyla uygulama" + native: "Doğal" +_gridComponent: + _error: + requiredValue: "Bu değer gereklidir." + columnTypeNotSupport: "Düzenli ifade ile doğrulama yalnızca type:text sütunları için desteklenir." + patternNotMatch: "Bu değer {pattern} içindeki desenle eşleşmiyor." + notUnique: "Bu değer benzersiz olmalıdır." +_roleSelectDialog: + notSelected: "Seçilmedi" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Seçili satırları kopyala" + copySelectionRanges: "Seçimi kopyala" + deleteSelectionRows: "Seçili satırları sil" + deleteSelectionRanges: "Seçimdeki satırları sil" + searchSettings: "Arama ayarları" + searchSettingCaption: "Ayrıntılı arama kriterleri belirle." + searchLimit: "Sonuç sayısı" + sortOrder: "Sıralama düzeni" + registrationLogs: "Kayıt günlüğü" + registrationLogsCaption: "Emojileri güncellerken veya silerken günlükler görüntülenecek. Güncelleme veya silme işleminden sonra, yeni bir sayfaya geçildiğinde veya yeniden yüklendiğinde günlükler kaybolacak." + alertEmojisRegisterFailedDescription: "Emojileri güncelleyemedi veya silemedi. Ayrıntılar için kayıt günlüğünü kontrol edin." + _logs: + showSuccessLogSwitch: "Başarı günlüğünü göster" + failureLogNothing: "Hata günlüğü yoktur." + logNothing: "Günlük kaydı yok." + _remote: + selectionRowDetail: "Seçilen satırın ayrıntıları" + importSelectionRows: "Seçilen satırları içe aktar" + importSelectionRangesRows: "Seçimdeki satırları içe aktar" + importEmojisButton: "Kontrol edilen Emojileri içe aktar" + confirmImportEmojisTitle: "Emoji'leri İçe Aktar" + confirmImportEmojisDescription: "Uzak sunucudan alınan {count} Emoji(ler)i içe aktarın. Emoji lisansına dikkat edin. Devam etmek istediğinizden emin misiniz?" + _local: + tabTitleList: "Kayıtlı emojiler" + tabTitleRegister: "Emoji kaydı" + _list: + emojisNothing: "Kayıtlı Emoji yok." + markAsDeleteTargetRows: "Silinecek hedef olarak seçilen satırları işaretle" + markAsDeleteTargetRanges: "Seçimdeki satırları silinecek hedef olarak işaretle" + alertUpdateEmojisNothingDescription: "Güncellenmiş Emoji yok." + alertDeleteEmojisNothingDescription: "Silinecek Emoji yok." + confirmMovePage: "Sayfaları taşımak ister misin?" + confirmChangeView: "Görüntüleme şeklini değiştirmek ister misn?" + confirmUpdateEmojisDescription: "{count} Emoji'yi güncelle. Devam etmek istediğinden emin misin?" + confirmDeleteEmojisDescription: "İşaretli {count} Emoji(leri) silin. Devam etmek istediğinden emin misin?" + confirmResetDescription: "Şimdiye kadar yapılan tüm değişiklikler geri alınacaktır." + confirmMovePageDesciption: "Bu sayfadaki Emojilerde değişiklikler yapılmış.\nSayfayı kaydetmeden terk ederseniz, bu sayfada yapılan tüm değişiklikler silinecek." + dialogSelectRoleTitle: "Emojilerde rol setine göre arama yapın" + _register: + uploadSettingTitle: "Yükleme ayarları" + uploadSettingDescription: "Bu ekranda, Emoji yüklerken davranışı yapılandırabilirsin." + directoryToCategoryLabel: "“Kategori” alanına dizin adını girin." + directoryToCategoryCaption: "Bir dizini sürükleyip bıraktığınızda, “kategori” alanına dizin adını girin." + confirmRegisterEmojisDescription: "Listeden Emojileri yeni özel Emojiler olarak kaydet. Devam etmek istediğinden emin misin? (Aşırı yüklemeyi önlemek için, tek bir işlemde yalnızca {count} Emoji kaydedilebilir)" + confirmClearEmojisDescription: "Düzenlemeleri sil ve listeden Emojileri temizle. Devam etmek istediğinden emin misiniz?" + confirmUploadEmojisDescription: "Drive'a sürüklenip bırakılan {count} dosyayı yükle. Devam etmek istediğinden emin misin?" +_embedCodeGen: + title: "Gömme kodunu özelleştir" + header: "Başlığı göster" + autoload: "Otomatik olarak daha fazlasını yükle (kullanımdan kaldırıldı)" + maxHeight: "Maksimum yükseklik" + maxHeightDescription: "0 olarak ayarlandığında maksimum yükseklik ayarı devre dışı bırakılır. Widget'ın dikey olarak genişlemeye devam etmesini önlemek için bir değer belirt." + maxHeightWarn: "Maksimum yükseklik sınırı devre dışıdır (0). Bu istenmeyen bir durumsa, maksimum yüksekliği bir değer olarak ayarla." + previewIsNotActual: "Ekran, önizleme ekranında görüntülenen aralığı aştığı için gerçek gömme işleminden farklıdır." + rounded: "Yuvarlak hale getir" + border: "Dış çerçeveye kenarlık ekle" + applyToPreview: "Önizlemeye başvur" + generateCode: "Gömme kodu oluştur" + codeGenerated: "Kod oluşturuldu" + codeGeneratedDescription: "Oluşturulan kodu web sitene yapıştırarak içeriği göm." +_selfXssPrevention: + warning: "UYARI" + title: "“Bu ekrana bir şey yapıştırın” tamamen bir aldatmaca." + description1: "Buraya bir şey yapıştırırsan, kötü niyetli bir kullanıcı hesabını ele geçirebilir veya kişisel bilgilerini çalabilir." + description2: "Yapıştırmaya çalıştığınız şeyi tam olarak anlamıyorsanız, %c hemen çalışmayı bırakın ve bu pencereyi kapatın." + description3: "Daha fazla bilgi için lütfen buraya bakın. {link}" +_followRequest: + recieved: "Talep alındı" + sent: "İstek gönderildi" +_remoteLookupErrors: + _federationNotAllowed: + title: "Bu sunucuyla iletişim kurulamıyor" + description: "Bu sunucu ile iletişim devre dışı bırakılmış olabilir veya bu sunucu engellenmiş olabilir.\nLütfen sunucu yöneticisi ile iletişime geçin." + _uriInvalid: + title: "URI geçersiz" + description: "Girdiğin URI ile ilgili bir sorun var. Lütfen URI'da kullanılamayan karakterler girip girmediğini kontrol et." + _requestFailed: + title: "İstek başarısız oldu" + description: "Bu sunucuyla iletişim kurulamadı. Sunucu kapalı olabilir. Ayrıca, geçersiz veya mevcut olmayan bir URI girmediğinizden emin ol." + _responseInvalid: + title: "Yanıt geçersiz" + description: "Bu sunucuyla iletişim kurabildi, ancak elde edilen veriler yanlıştı." + _noSuchObject: + title: "Bulunamadı" + description: "İstenen kaynak bulunamadı, lütfen URI'yi tekrar kontrol edin." +_captcha: + verify: "Lütfen CAPTCHA'yı doğrulayın" + testSiteKeyMessage: "Site ve gizli anahtarlar için test değerlerini girerek önizlemeyi kontrol edebilirsin.\nAyrıntılar için lütfen aşağıdaki sayfaya bak." + _error: + _requestFailed: + title: "CAPTCHA isteği başarısız oldu" + text: "Lütfen bir süre sonra tekrar çalıştırın veya ayarları tekrar kontrol edin." + _verificationFailed: + title: "CAPTCHA doğrulaması başarısız oldu" + text: "Ayarların doğru olup olmadığını lütfen tekrar kontrol edin." + _unknown: + title: "CAPTCHA hatası" + text: "Beklenmedik bir hata oluştu." +_bootErrors: + title: "Yükleme başarısız" + serverError: "Bir süre bekledikten ve yeniden yükledikten sonra sorun devam ederse, lütfen aşağıdaki Hata ID ile sunucu yöneticisine başvurun." + solution: "Aşağıdakiler sorunu çözebilir." + solution1: "Tarayıcını ve işletim sistemini en son sürüme güncelle." + solution2: "Reklam engelleyiciyi devre dışı bırak" + solution3: "Tarayıcı önbelleğini temizle" + solution4: "Tor Tarayıcı için dom.webaudio.enabled değerini true olarak ayarlayın." + otherOption: "Diğer seçenekler" + otherOption1: "İstemci ayarlarını ve önbelleği sil" + otherOption2: "Basit istemciyi başlatın" + otherOption3: "Onarım aracını başlatın" + otherOption4: "Misskey'i güvenli modda başlatın" _search: searchScopeAll: "Tümü" + searchScopeLocal: "Yerel" + searchScopeServer: "Spesifik sunucu" + searchScopeUser: "Spesifik kullanıcı" + pleaseEnterServerHost: "Sunucu ana bilgisayarını girin" + pleaseSelectUser: "Kullanıcı seç" + serverHostPlaceholder: "Örnek: misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey kurulumu tamamlandı!" + firstCreateAccount: "Başlamak için bir yönetici hesabı oluşturun." + accountCreated: "Yönetici hesabı oluşturuldu!" + serverSetting: "Sunucu Ayarları" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Bu sihirbaz, sunucu ayarlarını yapılandırmayı kolaylaştırır." + settingsYouMakeHereCanBeChangedLater: "Bu sihirbaz aracılığıyla değiştirilen ayarlar daha sonra yeniden düzenlenebilir." + howWillYouUseMisskey: "Misskey'i nasıl kullanacaksınız?" + _use: + single: "Tek Kullanıcı Sunucusu" + single_description: "Kendi sunucunuz olarak tek başına kullanın." + single_youCanCreateMultipleAccounts: "Tek kullanıcı sunucusu olarak çalıştırıldığında bile, gerektiğinde birden fazla hesap oluşturulabilir." + group: "Grup sunucusu" + group_description: "Diğer güvenilir kullanıcıları birden fazla kullanıcıyla birlikte kullanmaya davet edin." + open: "Genel sunucu" + open_description: "Herkesin kayıt olmasına izin verin." + openServerAdvice: "Çok sayıda bilinmeyen kullanıcıyı kabul etmek risklidir. Herhangi bir sorunu çözmek için güvenilir bir moderasyon sistemi kullanmanızı öneririz." + openServerAntiSpamAdvice: "Sunucunuzun spam için bir basamak haline gelmesini önlemek için, reCAPTCHA gibi anti-bot işlevlerini etkinleştirerek güvenliğe de özen göstermelisin." + howManyUsersDoYouExpect: "Kaç kullanıcı bekliyorsunuz?" + _scale: + small: "100'den az (küçük ölçekli)" + medium: "100'den fazla ve 1000'den az kullanıcı (orta büyüklükte)" + large: "1000'den fazla (Büyük ölçekli)" + largeScaleServerAdvice: "Büyük sunucular, yük dengeleme ve veritabanı çoğaltma gibi gelişmiş altyapı bilgisi gerektirebilir." + doYouConnectToFediverse: "Fediverse'e bağlanmak ister misin?" + doYouConnectToFediverse_description1: "Dağıtılmış sunucular ağına (Fediverse) bağlandığında, içerik diğer sunucularla paylaşılabilir." + doYouConnectToFediverse_description2: "Fediverse ile bağlantı kurmak “federasyon” olarak da adlandırılır." + youCanConfigureMoreFederationSettingsLater: "Birleştirilmiş sunucuları belirtme gibi gelişmiş ayarlar daha sonra yapılandırılabilir." + remoteContentsCleaning: "Alınan içeriklerin otomatik olarak temizlenmesi" + remoteContentsCleaning_description: "Federasyon, sürekli içerik akışına neden olabilir. Otomatik temizleme özelliğini etkinleştirmek, depolama alanından tasarruf etmek için sunucudan eski ve referanslanmamış içeriği kaldıracak." + adminInfo: "Yönetici bilgileri" + adminInfo_description: "Sorguları almak için kullanılan yönetici bilgilerini ayarlar." + adminInfo_mustBeFilled: "Genel sunucu veya federasyon açıksa girilmelidir." + followingSettingsAreRecommended: "Aşağıdaki ayarlar önerilir" + applyTheseSettings: "Bu ayarları uygulayın" + skipSettings: "Ayarları atla" + settingsCompleted: "Kurulum tamamlandı!" + settingsCompleted_description: "Zaman ayırdığınız için teşekkür ederiz. Artık her şey hazır olduğuna göre, sunucuyu hemen kullanmaya başlayabilirsin." + settingsCompleted_description2: "Sunucu ayarları “Kontrol Paneli”nden değiştirilebilir." + donationRequest: "Bağış Talebi" + _donationRequest: + text1: "Misskey, gönüllüler tarafından geliştirilen ücretsiz bir yazılımdır." + text2: "Bu yazılımı gelecekte de geliştirmeye devam edebilmemiz için desteğini rica ederiz." + text3: "Destekçilere özel avantajlar da var!" +_uploader: + editImage: "Resmi Düzenle" + compressedToX: "{x} boyutuna sıkıştırıldı" + savedXPercent: "{x}% tasarruf" + abortConfirm: "Bazı dosyalar yüklenmedi, iptal etmek ister misin?" + doneConfirm: "Bazı dosyalar yüklenmedi, yine de devam etmek istiyor musun?" + maxFileSizeIsX: "Yükleyebileceğin maksimum dosya boyutu {x}" + allowedTypes: "Yüklenebilir dosya türleri" + tip: "Dosya henüz yüklenmediğinden, bu iletişim kutusu yüklemeden önce dosyayı onaylamanıza, yeniden adlandırmana, sıkıştırmana ve kırpmana olanak tanır. Hazır olduğunda, “Yükle” düğmesine basarak yüklemeyi başlatabilirsin." +_clientPerformanceIssueTip: + title: "Performans ipuçları" + makeSureDisabledAdBlocker: "Reklam engelleyicini devre dışı bırak" + makeSureDisabledAdBlocker_description: "Reklam engelleyiciler performansı etkileyebilir, lütfen sisteminde veya tarayıcının özelliklerinde/uzantılarında reklam engelleyicilerin etkinleştirilmediğinden emin ol." + makeSureDisabledCustomCss: "Özel CSS'yi devre dışı bırak" + makeSureDisabledCustomCss_description: "Stil geçersiz kılma, performansı etkileyebilir. Stil geçersiz kılan özel CSS veya uzantıların etkinleştirilmediğinden emin ol." + makeSureDisabledAddons: "Uzantıları devre dışı bırak" + makeSureDisabledAddons_description: "Bazı uzantılar istemci davranışını engelleyebilir ve performansı etkileyebilir. Lütfen tarayıcı uzantılarınızı devre dışı bırakın ve durumun düzelip düzelmediğini kontrol edin." +_clip: + tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." +_userLists: + tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir pano olarak görüntülenebilir." +watermark: "Filigran" +defaultPreset: "Varsayılan Ön Ayar" +_watermarkEditor: + tip: "Kredi bilgileri gibi bir filigran görüntüye eklenebilir." + quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misin?" + driveFileTypeWarn: "Bu dosya desteklenmiyor" + driveFileTypeWarnDescription: "Bir görüntü dosyası seçin" + title: "Filigranı Düzenle" + cover: "Her şeyi örtün" + repeat: "her yere yayılmış" + opacity: "Opaklık" + scale: "Boyut" + text: "Metin" + position: "Pozisyon" + type: "Tür" + image: "Görseller" + advanced: "Gelişmiş" + angle: "Açı" + stripe: "Çizgiler" + stripeWidth: "Çizgi genişliği" + stripeFrequency: "Satır sayısı" + polkadot: "Nokta deseni" + checker: "Kontrolcü" + polkadotMainDotOpacity: "Ana noktanın opaklığı" + polkadotMainDotRadius: "Ana noktanın boyutu" + polkadotSubDotOpacity: "İkincil noktanın opaklığı" + polkadotSubDotRadius: "İkincil noktanın boyutu" + polkadotSubDotDivisions: "Alt nokta sayısı." +_imageEffector: + title: "Effektler" + addEffect: "Efektler Ekle" + discardChangesConfirm: "Cidden çıkmak istiyor musun? Kaydedilmemiş değişikliklerin var." + _fxs: + chromaticAberration: "Renk Sapması" + glitch: "Bozulma" + mirror: "Ayna" + invert: "Renkleri Ters Çevir" + grayscale: "Gri tonlama" + colorAdjust: "Renk Düzeltme" + colorClamp: "Renk Sıkıştırma" + colorClampAdvanced: "Renk Sıkıştırma (Gelişmiş)" + distort: "Bozulma" + threshold: "Binarize" + zoomLines: "Doymuş hatlar" + stripe: "Çizgiler" + polkadot: "Nokta deseni" + checker: "Denetleyici" + blockNoise: "Gürültüyü Engelle" + tearing: "Yırtılma" + _fxProps: + angle: "Açı" + scale: "Boyut" + size: "Boyut" + offset: "Pozisyon" + color: "Renk" + opacity: "Opaklık" + normalize: "Normalize" + amount: "Miktar" + lightness: "Hafiflet" + contrast: "Kontrast" + hue: "Hue" + brightness: "Parlaklık" + saturation: "Doygunluk" + max: "Maksimum" + min: "Minimum" + direction: "Yön" + phase: "Aşama" + frequency: "Sıklık" + strength: "Güç" + glitchChannelShift: "Kanal değişimi" + seed: "Tohum değeri" + redComponent: "Kırmızı bileşen" + greenComponent: "Yeşil bileşen" + blueComponent: "Mavi bileşen" + threshold: "Eşik" + centerX: "Merkez X" + centerY: "Merkez Y" + zoomLinesSmoothing: "Düzeltme" + zoomLinesSmoothingDescription: "Düzeltme ve yakınlaştırma çizgi genişliği birlikte kullanılamaz." + zoomLinesThreshold: "Zoom çizgi genişliği" + zoomLinesMaskSize: "Merkez çapı" + zoomLinesBlack: "Siyah yap" +drafts: "Taslaklar" +_drafts: + select: "Taslak Seç" + cannotCreateDraftAnymore: "Oluşturulabilecek taslak sayısı aşılmıştır." + cannotCreateDraft: "Bu içerikle taslak oluşturamazsınız." + delete: "Taslak Sil" + deleteAreYouSure: "Taslağı silmek ister misin?" + noDrafts: "Taslak yok" + replyTo: "{user} notunu yanıtla" + quoteOf: "{user} notuna alıntı" + postTo: "{channel}'a gönder" + saveToDraft: "Taslak olarak kaydet" + restoreFromDraft: "Taslaktan geri yükle" + restore: "Geri yükle" + listDrafts: "Taslaklar Listesi" +_qr: + showTabTitle: "Ekran" + raw: "Metin" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 6a970fadfb..273ccfb078 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -8,6 +8,9 @@ search: "Пошук" notifications: "Сповіщення" username: "Ім'я користувача" password: "Пароль" +initialPasswordForSetup: "Початковий пароль для налаштування" +initialPasswordIsIncorrect: "Початковий пароль для налаштування неправильний" +initialPasswordForSetupDescription: "Використайте пароль, вказаний у конфігураційному файлі, якщо ви встановлювали Misskey власноруч.\nЯкщо використовуєте сервіси хостингу Misskey, використайте наданий пароль.\nЯкщо ви не маєте паролю, лишіть порожнім щоб продовжити. " forgotPassword: "Я забув пароль" fetchingAsApObject: "Отримуємо з федіверсу..." ok: "OK" @@ -45,6 +48,7 @@ pin: "Закріпити" unpin: "Відкріпити" copyContent: "Скопіювати контент" copyLink: "Скопіювати посилання" +copyRemoteLink: "Копіювати віддалене посилання" delete: "Видалити" deleteAndEdit: "Видалити й редагувати" deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї." @@ -57,6 +61,7 @@ copyUserId: "Копіювати ID користувача" copyNoteId: "блокнот ID користувача" copyFileId: "Скопіювати ідентифікатор файлу." searchUser: "Пошук користувачів" +searchThisUsersNotes: "Пошук нотаток користувача" reply: "Відповісти" loadMore: "Показати більше" showMore: "Показати більше" @@ -105,9 +110,11 @@ enterEmoji: "Введіть емодзі" renote: "Поширити" unrenote: "Відміна поширення" renoted: "Поширити запис." +renotedToX: "Поширено до {name}" cantRenote: "Неможливо поширити." cantReRenote: "Поширення не можливо поширити." quote: "Цитата" +inChannelRenote: "Поширено у канал" pinnedNote: "Закріплений запис" pinned: "Закріпити" you: "Ви" @@ -116,6 +123,7 @@ sensitive: "NSFW" add: "Додати" reaction: "Реакції" reactions: "Реакції" +emojiPicker: "Вибір реакції" reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати." rememberNoteVisibility: "Пам’ятати параметри видимісті" attachCancel: "Видалити вкладення" @@ -208,7 +216,6 @@ noUsers: "Немає користувачів" editProfile: "Редагувати обліковий запис" noteDeleteConfirm: "Ви дійсно хочете видалити цей запис?" pinLimitExceeded: "Більше записів не можна закріпити" -intro: "Встановлення Misskey завершено! Будь ласка, створіть обліковий запис адміністратора." done: "Готово" processing: "Обробка" preview: "Попередній перегляд" @@ -290,7 +297,9 @@ folderName: "Ім'я теки" createFolder: "Створити теку" renameFolder: "Перейменувати теку" deleteFolder: "Видалити теку" +folder: "Тека" addFile: "Додати файл" +showFile: "Показати файл" emptyDrive: "Диск порожній" emptyFolder: "Тека порожня" unableToDelete: "Видалення неможливе" @@ -303,6 +312,7 @@ copyUrl: "Копіювати URL" rename: "Перейменувати" avatar: "Аватар" banner: "Банер" +displayOfSensitiveMedia: "Показ чутливого медіа" whenServerDisconnected: "Коли зв’язок із сервером втрачено" disconnectedFromServer: "Зв’язок із сервером було перервано" reload: "Оновити" @@ -349,8 +359,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "Увімкнути hCaptcha" hcaptchaSiteKey: "Ключ сайту" hcaptchaSecretKey: "Секретний ключ" +mcaptcha: "MCaptcha" +enableMcaptcha: "Увімкнути MCaptcha" mcaptchaSiteKey: "Ключ сайту" mcaptchaSecretKey: "Секретний ключ" +mcaptchaInstanceUrl: "Посилання на сервер MCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Увімкнути reCAPTCHA" recaptchaSiteKey: "Ключ сайту" @@ -681,7 +694,6 @@ experimentalFeatures: "Експериментальні функції" developer: "Розробник" makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\"" makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі \"Огляд\"." -showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин" duplicate: "Дублікат" left: "Лівий" center: "Центр" @@ -907,6 +919,17 @@ flip: "Перевернути" lastNDays: "Останні {n} днів" postForm: "Створення нотатки" information: "Інформація" +inMinutes: "х" +inDays: "д" +widgets: "Віджети" +_imageEditing: + _vars: + filename: "Ім'я файлу" +_imageFrameEditor: + header: "Заголовок" + font: "Шрифт" + fontSerif: "Serif" + fontSansSerif: "Sans serif" _chat: invitations: "Запросити" noHistory: "Історія порожня" @@ -1304,7 +1327,6 @@ _theme: buttonBg: "Фон кнопки" buttonHoverBg: "Фон кнопки (при наведенні)" inputBorder: "Край поля вводу" - driveFolderBg: "Фон папки на диску" badge: "Значок" messageBg: "Фон переписки" fgHighlighted: "Виділений текст" @@ -1408,6 +1430,14 @@ _widgets: userList: "Список користувачів" _userList: chooseList: "Виберіть список" +_widgetOptions: + height: "Висота" + _button: + colored: "Кольоровий" + _clock: + size: "Розмір" + _birthdayFollowings: + period: "Тривалість" _cw: hide: "Сховати" show: "Показати більше" @@ -1448,6 +1478,9 @@ _postForm: replyPlaceholder: "Відповідь на цю нотатку..." quotePlaceholder: "Прокоментуйте цю нотатку..." channelPlaceholder: "Опублікувати в каналі" + _howToUse: + visibility_title: "Видимість" + menu_title: "Меню" _placeholders: a: "Чим займаєтесь?" b: "Що відбувається навколо вас?" @@ -1628,3 +1661,20 @@ _remoteLookupErrors: _search: searchScopeAll: "Всі" searchScopeLocal: "Локальна" +_watermarkEditor: + opacity: "Непрозорість" + scale: "Розмір" + text: "Текст" + type: "Тип" + image: "Зображення" + advanced: "Розширені" +_imageEffector: + _fxProps: + scale: "Розмір" + size: "Розмір" + color: "Колір" + opacity: "Непрозорість" + lightness: "Яскравість" +_qr: + showTabTitle: "Відображення" + raw: "Текст" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 8289a6d60c..11db034823 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -219,7 +219,6 @@ noUsers: "Foydalanuvchilar yo‘q" editProfile: "Profilni o'zgartirish" noteDeleteConfirm: "Haqiqatan ham bu qaydni oʻchirib tashlamoqchimisiz?" pinLimitExceeded: "Siz boshqa qaydlarni mahkamlay olmaysiz" -intro: "Misskeyni o'rnatish tugallandi! Iltimos, administrator foydalanuvchi yarating." done: "Bajarildi" processing: "Amaliyotda" preview: "Ko'rish" @@ -838,6 +837,14 @@ replies: "Javob berish" renotes: "Qayta qayd etish" flip: "Teskari" information: "Haqida" +_imageEditing: + _vars: + filename: "Fayl nomi" +_imageFrameEditor: + header: "Sarlavha" + font: "Shrift" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "Taklif qilish" noHistory: "Tarix yo'q" @@ -904,7 +911,7 @@ _theme: header: "Sarlavha" navBg: "Yon panel foni" navFg: "Yon panel matni" - mention: "Murojat" + mention: "Eslatib o'tish" renote: "Qayta qayd etish" divider: "Ajratrmoq" fgHighlighted: "Belgilangan matn" @@ -938,6 +945,12 @@ _widgets: jobQueue: "Vazifalar navbati" _userList: chooseList: "Ro'yxat tanlash" +_widgetOptions: + height: "balandligi" + _button: + colored: "rangli" + _birthdayFollowings: + period: "Davomiylik" _cw: show: "Ko‘proq ko‘rish" chars: "{count} ta belgi(lar)" @@ -965,6 +978,10 @@ _visibility: home: "Bosh sahifa" followers: "Obunachilar" specified: "Bevosita" +_postForm: + _howToUse: + visibility_title: "Ko'rinishi" + menu_title: "Menyu" _profile: name: "Ism" username: "Foydalanuvchi nomi" @@ -1046,7 +1063,7 @@ _notification: _types: all: "Barchasi" follow: "Obuna bo‘lish" - mention: "Murojat" + mention: "Eslatib o'tish" renote: "Qayta qayd etish" quote: "Iqtibos keltirish" reaction: "Reaktsiyalar" @@ -1098,3 +1115,15 @@ _remoteLookupErrors: _search: searchScopeAll: "Barcha" searchScopeLocal: "Mahalliy" +_watermarkEditor: + text: "Matn" + type: "turi" + image: "Rasmlar" + advanced: "Murakkab" +_imageEffector: + _fxProps: + color: "Rang" + lightness: "Yoritish" +_qr: + showTabTitle: "Displey" + raw: "Matn" diff --git a/locales/verify.js b/locales/verify.js deleted file mode 100644 index a8e9875d6e..0000000000 --- a/locales/verify.js +++ /dev/null @@ -1,53 +0,0 @@ -import locales from './index.js'; - -let valid = true; - -function writeError(type, lang, tree, data) { - process.stderr.write(JSON.stringify({ type, lang, tree, data })); - process.stderr.write('\n'); - valid = false; -} - -function verify(expected, actual, lang, trace) { - for (let key in expected) { - if (!Object.prototype.hasOwnProperty.call(actual, key)) { - continue; - } - if (typeof expected[key] === 'object') { - if (typeof actual[key] !== 'object') { - writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'object', actual: typeof actual[key] }); - continue; - } - verify(expected[key], actual[key], lang, trace ? `${trace}.${key}` : key); - } else if (typeof expected[key] === 'string') { - switch (typeof actual[key]) { - case 'object': - writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'string', actual: 'object' }); - break; - case 'undefined': - continue; - case 'string': - const expectedParameters = new Set(expected[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1))); - const actualParameters = new Set(actual[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1))); - for (let parameter of expectedParameters) { - if (!actualParameters.has(parameter)) { - writeError('missing_parameter', lang, trace ? `${trace}.${key}` : key, { parameter }); - } - } - } - } - } -} - -const { ['ja-JP']: original, ...verifiees } = locales; - -for (let lang in verifiees) { - if (!Object.prototype.hasOwnProperty.call(locales, lang)) { - continue; - } - verify(original, verifiees[lang], lang); -} - -if (!valid) { - process.exit(1); -} diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 16917ebf06..0bb1d54ae8 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1,10 +1,11 @@ --- -_lang_: "Tiếng Nhật" +_lang_: "Tiếng Việt " headlineMisskey: "Mạng xã hội liên hợp" introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀" poweredByMisskeyDescription: "{name} là một trong những chủ máy của Misskey là nền tảng mã nguồn mở" monthAndDay: "{day} tháng {month}" search: "Tìm kiếm" +reset: "cài lại" notifications: "Thông báo" username: "Tên người dùng" password: "Mật khẩu" @@ -48,9 +49,10 @@ pin: "Ghim" unpin: "Bỏ ghim" copyContent: "Chép nội dung" copyLink: "Chép liên kết" +copyRemoteLink: "Sao chép liên kết từ xa" copyLinkRenote: "Sao chép liên kết ghi chú" delete: "Xóa" -deleteAndEdit: "Sửa" +deleteAndEdit: "Xóa và soạn thảo lại" deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất." addToList: "Thêm vào danh sách" addToAntenna: "Thêm vào Ăngten" @@ -63,6 +65,7 @@ copyFileId: "Sao chép ID tập tin" copyFolderId: "Sao chép ID thư mục" copyProfileUrl: "Sao chép URL hồ sơ" searchUser: "Tìm kiếm người dùng" +searchThisUsersNotes: "Tìm kiếm ghi chú của người dùng" reply: "Trả lời" loadMore: "Tải thêm" showMore: "Xem thêm" @@ -99,7 +102,7 @@ pageLoadErrorDescription: "Có thể là do bộ nhớ đệm của trình duy serverIsDead: "Máy chủ không phản hồi. Vui lòng thử lại sau giây lát." youShouldUpgradeClient: "Để xem trang này, hãy làm tươi để cập nhật ứng dụng." enterListName: "Đặt tên cho danh sách" -privacy: "Bảo mật" +privacy: "Riêng tư" makeFollowManuallyApprove: "Yêu cầu theo dõi cần được duyệt" defaultNoteVisibility: "Kiểu tút mặc định" follow: "Theo dõi" @@ -111,11 +114,14 @@ enterEmoji: "Chèn emoji" renote: "Đăng lại" unrenote: "Hủy đăng lại" renoted: "Đã đăng lại." +renotedToX: "Đã cho thuê lại {name}." cantRenote: "Không thể đăng lại tút này." cantReRenote: "Không thể đăng lại một tút đăng lại." quote: "Trích dẫn" inChannelRenote: "Chia sẻ trong kênh này" inChannelQuote: "Trích dẫn trong kênh này" +renoteToChannel: "Đăng lại tới kênh" +renoteToOtherChannel: "Đăng lại tới kênh khác" pinnedNote: "Bài viết đã ghim" pinned: "Ghim" you: "Bạn" @@ -125,6 +131,11 @@ add: "Thêm" reaction: "Biểu cảm" reactions: "Biểu cảm" emojiPicker: "Bộ chọn biểu tượng cảm xúc" +pinnedEmojisForReactionSettingDescription: "Ghim các biểu tượng cảm xúc sẽ hiển thị khi phản hồi" +pinnedEmojisSettingDescription: "Ghim các biểu tượng cảm xúc sẽ hiển thị trong bảng chọn emoji" +emojiPickerDisplay: "Hiển thị bộ chọn" +overwriteFromPinnedEmojisForReaction: "Ghi đè thiết lập phản hồi" +overwriteFromPinnedEmojis: "Ghi đè thiết lập chung" reactionSettingDescription2: "Kéo để sắp xếp, nhấn để xóa, nhấn \"+\" để thêm." rememberNoteVisibility: "Lưu kiểu tút mặc định" attachCancel: "Gỡ tập tin đính kèm" @@ -149,6 +160,7 @@ editList: "Chỉnh sửa danh sách" selectChannel: "Lựa chọn kênh" selectAntenna: "Chọn một antenna" editAntenna: "Chỉnh sửa Ăngten" +createAntenna: "Tạo Ăngten " selectWidget: "Chọn tiện ích" editWidgets: "Sửa tiện ích" editWidgetsExit: "Xong" @@ -175,6 +187,10 @@ addAccount: "Thêm tài khoản" reloadAccountsList: "Cập nhật danh sách tài khoản" loginFailed: "Đăng nhập không thành công" showOnRemote: "Truy cập trang của người này" +continueOnRemote: "Tiếp tục trên phiên bản từ xa" +chooseServerOnMisskeyHub: "Chọn một máy chủ từ Misskey Hub" +specifyServerHost: "Thiết lập một máy chủ" +inputHostName: "Nhập địa chỉ máy chủ" general: "Tổng quan" wallpaper: "Ảnh bìa" setWallpaper: "Đặt ảnh bìa" @@ -185,6 +201,7 @@ followConfirm: "Bạn theo dõi {name}?" proxyAccount: "Tài khoản proxy" proxyAccountDescription: "Tài khoản proxy là tài khoản hoạt động như một người theo dõi từ xa cho người dùng trong những điều kiện nhất định. Ví dụ: khi người dùng thêm người dùng từ xa vào danh sách, hoạt động của người dùng từ xa sẽ không được chuyển đến phiên bản nếu không có người dùng cục bộ nào theo dõi người dùng đó, vì vậy tài khoản proxy sẽ theo dõi." host: "Host" +selectSelf: "Chọn chính bạn" selectUser: "Chọn người dùng" recipient: "Người nhận" annotation: "Bình luận" @@ -199,8 +216,11 @@ perHour: "Mỗi Giờ" perDay: "Mỗi Ngày" stopActivityDelivery: "Ngưng gửi hoạt động" blockThisInstance: "Chặn máy chủ này" +silenceThisInstance: "Máy chủ im lặng" +mediaSilenceThisInstance: "Tắt nội dung đa phương tiện từ máy chủ này" operations: "Vận hành" software: "Phần mềm" +softwareName: "Tên phần mềm" version: "Phiên bản" metadata: "Metadata" withNFiles: "{n} tập tin" @@ -218,6 +238,12 @@ clearCachedFiles: "Xóa bộ nhớ đệm" clearCachedFilesConfirm: "Bạn có chắc muốn xóa sạch bộ nhớ đệm?" blockedInstances: "Máy chủ đã chặn" blockedInstancesDescription: "Danh sách những máy chủ bạn muốn chặn. Chúng sẽ không thể giao tiếp với máy chủy này nữa." +silencedInstances: "Máy chủ im lặng" +silencedInstancesDescription: "Đặt máy chủ mà bạn muốn tắt tiếng, phân tách bằng dấu xuống dòng. Tất cả tài khoản trên máy chủ bị tắt tiếng sẽ được coi là \"bị tắt tiếng\" và mọi hành động theo dõi sẽ được coi là yêu cầu. Không có tác dụng với những trường hợp bị chặn." +mediaSilencedInstances: "Các máy chủ đã tắt nội dung đa phương tiện " +mediaSilencedInstancesDescription: "Đặt máy chủ mà bạn muốn tắt nội dung đa phương tiện, phân tách bằng dấu xuống dòng. Tất cả tài khoản trên máy chủ bị tắt tiếng sẽ được coi là \"nhạy cảm\" và biểu tượng cảm xúc tùy chỉnh sẽ không thể được sử dụng. Không có tác dụng với những trường hợp bị chặn." +federationAllowedHosts: "Các máy chủ được phép liên kết" +federationAllowedHostsDescription: "Điền tên các máy chủ mà bạn muốn cho phép liên kết, cách nhau bởi dấu xuống dòng" muteAndBlock: "Ẩn và Chặn" mutedUsers: "Người đã ẩn" blockedUsers: "Người đã chặn" @@ -225,7 +251,6 @@ noUsers: "Chưa có ai" editProfile: "Sửa hồ sơ" noteDeleteConfirm: "Bạn có chắc muốn xóa tút này?" pinLimitExceeded: "Bạn không thể ghim bài viết nữa" -intro: "Đã cài đặt Misskey! Xin hãy tạo tài khoản admin." done: "Xong" processing: "Đang xử lý" preview: "Xem trước" @@ -254,8 +279,8 @@ more: "Thêm nữa!" featured: "Nổi bật" usernameOrUserId: "Tên người dùng hoặc ID" noSuchUser: "Không tìm thấy người dùng" -lookup: "Tìm kiếm" -announcements: "Thông báo" +lookup: "Tra cứu" +announcements: "Thông báo máy chủ" imageUrl: "URL ảnh" remove: "Xóa" removed: "Đã xóa" @@ -273,9 +298,11 @@ uploadFromUrl: "Tải lên bằng một URL" uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên" uploadFromUrlRequested: "Đã yêu cầu tải lên" uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong." +uploadNFiles: "Tải lên {n} tập tin" explore: "Khám phá" messageRead: "Đã đọc" noMoreHistory: "Không còn gì để đọc" +startChat: "Bắt đầu trò chuyện" nUsersRead: "đọc bởi {n}" agreeTo: "Tôi đồng ý {0}" agree: "Đồng ý" @@ -306,6 +333,7 @@ selectFile: "Chọn tập tin" selectFiles: "Chọn nhiều tập tin" selectFolder: "Chọn thư mục" selectFolders: "Chọn nhiều thư mục" +fileNotSelected: "Chưa chọn tệp nào" renameFile: "Đổi tên tập tin" folderName: "Tên thư mục" createFolder: "Tạo thư mục" @@ -313,6 +341,7 @@ renameFolder: "Đổi tên thư mục" deleteFolder: "Xóa thư mục" folder: "Thư mục" addFile: "Thêm tập tin" +showFile: "Hiển thị tập tin" emptyDrive: "Ổ đĩa của bạn trống trơn" emptyFolder: "Thư mục trống" unableToDelete: "Không thể xóa" @@ -396,6 +425,7 @@ antennaExcludeBots: "Loại trừ các tài khoản bot" antennaKeywordsDescription: "Phân cách bằng dấu cách cho điều kiện AND hoặc bằng xuống dòng cho điều kiện OR." notifyAntenna: "Thông báo có tút mới" withFileAntenna: "Chỉ những tút có media" +excludeNotesInSensitiveChannel: "Không hiển thị trong kênh nhạy cảm" enableServiceworker: "Bật ServiceWorker" antennaUsersDescription: "Liệt kê mỗi hàng một tên người dùng" caseSensitive: "Trường hợp nhạy cảm" @@ -426,6 +456,7 @@ totpDescription: "Nhắn mã OTP bằng ứng dụng xác thực" moderator: "Kiểm duyệt viên" moderation: "Kiểm duyệt" moderationNote: "Ghi chú kiểm duyệt" +moderationNoteDescription: "Bạn có thể điền vào những ghi chú chỉ được chia sẻ giữa những người kiểm duyệt." addModerationNote: "Thêm ghi chú kiểm duyệt" moderationLogs: "Nhật kí quản trị" nUsersMentioned: "Dùng bởi {n} người" @@ -463,6 +494,7 @@ quoteQuestion: "Trích dẫn lại?" attachAsFileQuestion: "Văn bản ở trong bộ nhớ tạm rất dài. Bạn có muốn đăng nó dưới dạng một tệp văn bản không?" onlyOneFileCanBeAttached: "Bạn chỉ có thể đính kèm một tập tin" signinRequired: "Vui lòng đăng nhập" +signinOrContinueOnRemote: "Để tiếp tục, bạn cần chuyển máy chủ hoặc đăng nhập/đăng ký ở máy chủ này." invitations: "Mời" invitationCode: "Mã mời" checking: "Đang kiểm tra..." @@ -484,7 +516,12 @@ uiLanguage: "Ngôn ngữ giao diện" aboutX: "Giới thiệu {x}" emojiStyle: "Kiểu cách Emoji" native: "Bản xứ" +menuStyle: "Kiểu Menu" +style: "Phong cách" +drawer: "Ngăn ứng dụng" +popup: "Cửa sổ bật lên" showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột" +showReactionsCount: "Hiển thị số reaction trong bài đăng" noHistory: "Không có dữ liệu" signinHistory: "Lịch sử đăng nhập" enableAdvancedMfm: "Xem bài MFM chất lượng cao." @@ -497,6 +534,7 @@ createAccount: "Tạo tài khoản" existingAccount: "Tài khoản hiện có" regenerate: "Tạo lại" fontSize: "Cỡ chữ" +mediaListWithOneImageAppearance: "Chiều cao của danh sách nội dung đã phương tiện mà chỉ có một hình ảnh" limitTo: "Giới hạn tỷ lệ {x}" noFollowRequests: "Bạn không có yêu cầu theo dõi nào" openImageInNewTab: "Mở ảnh trong tab mới" @@ -531,13 +569,16 @@ objectStorageUseSSLDesc: "Tắt nếu bạn không dùng HTTPS để kết nối objectStorageUseProxy: "Kết nối thông qua Proxy" objectStorageUseProxyDesc: "Tắt nếu bạn không dùng Proxy để kết nối API" objectStorageSetPublicRead: "Đặt \"public-read\" khi tải lên" +s3ForcePathStyleDesc: "Nếu s3ForcePathStyle được bật, tên bucket phải được thêm vào địa chỉ URL thay vì chỉ có tên miền. Bạn có thể phải sử dụng thiết lập này nếu bạn sử dụng các dịch vụ như Minio mà bạn tự cung cấp." serverLogs: "Nhật ký máy chủ" deleteAll: "Xóa tất cả" showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin" showFixedPostFormInChannel: "Hiển thị mẫu bài đăng ở phía trên bản tin" +withRepliesByDefaultForNewlyFollowed: "Mặc định hiển thị trả lời từ những người dùng mới theo dõi trong dòng thời gian" newNoteRecived: "Đã nhận tút mới" sounds: "Âm thanh" sound: "Âm thanh" +notificationSoundSettings: "Cài đặt âm thanh thông báo" listen: "Nghe" none: "Không" showInPage: "Hiện trong trang" @@ -545,7 +586,9 @@ popout: "Pop-out" volume: "Âm lượng" masterVolume: "Âm thanh chung" notUseSound: "Tắt tiếng" +useSoundOnlyWhenActive: "Chỉ phát âm thanh khi Misskey đang được hiển thị" details: "Chi tiết" +renoteDetails: "Tìm hiểu thêm về đăng lại " chooseEmoji: "Chọn emoji" unableToProcess: "Không thể hoàn tất hành động" recentUsed: "Sử dụng gần đây" @@ -561,6 +604,7 @@ ascendingOrder: "Tăng dần" descendingOrder: "Giảm dần" scratchpad: "Scratchpad" scratchpadDescription: "Scratchpad cung cấp môi trường cho các thử nghiệm AiScript. Bạn có thể viết, thực thi và kiểm tra kết quả tương tác với Misskey trong đó." +uiInspector: "Trình kiểm tra UI" output: "Nguồn ra" script: "Kịch bản" disablePagesScript: "Tắt AiScript trên Trang" @@ -619,6 +663,7 @@ medium: "Vừa" small: "Nhỏ" generateAccessToken: "Tạo mã truy cập" permission: "Cho phép " +adminPermission: "Quyền quản trị viên" enableAll: "Bật toàn bộ" disableAll: "Tắt toàn bộ" tokenRequested: "Cấp quyền truy cập vào tài khoản" @@ -640,13 +685,19 @@ smtpSecure: "Dùng SSL/TLS ngầm định cho các kết nối SMTP" smtpSecureInfo: "Tắt cái này nếu dùng STARTTLS" testEmail: "Kiểm tra vận chuyển email" wordMute: "Ẩn chữ" +wordMuteDescription: "Thu nhỏ các bài đăng chứa các từ hoặc cụm từ nhất định. Các bài đăng này có thể được hiển thị khi click vào." +hardWordMute: "Ẩn cụm từ hoàn toàn" +showMutedWord: "Hiển thị từ đã ẩn" +hardWordMuteDescription: "Ẩn hoàn toàn các bài đăng chứa từ hoặc cụm từ. Khác với mute, bài đăng sẽ bị ẩn hoàn toàn." regexpError: "Lỗi biểu thức" regexpErrorDescription: "Xảy ra lỗi biểu thức ở dòng {line} của {tab} chữ ẩn:" instanceMute: "Những máy chủ ẩn" userSaysSomething: "{name} nói gì đó" +userSaysSomethingAbout: "{name} đã nói gì đó về \"{word}\"" makeActive: "Kích hoạt" display: "Hiển thị" copy: "Sao chép" +copiedToClipboard: "Đã sao chép vào clipboard" metrics: "Số liệu" overview: "Tổng quan" logs: "Nhật ký" @@ -661,12 +712,14 @@ useGlobalSettingDesc: "Nếu được bật, cài đặt thông báo của bạn other: "Khác" regenerateLoginToken: "Tạo lại mã đăng nhập" regenerateLoginTokenDescription: "Tạo lại mã nội bộ có thể dùng để đăng nhập. Thông thường hành động này là không cần thiết. Nếu được tạo lại, tất cả các thiết bị sẽ bị đăng xuất." +theKeywordWhenSearchingForCustomEmoji: "Đây là từ khoá được sử dụng để tìm kiếm emoji" setMultipleBySeparatingWithSpace: "Tách nhiều mục nhập bằng dấu cách." fileIdOrUrl: "ID tập tin hoặc URL" behavior: "Thao tác" sample: "Ví dụ" abuseReports: "Lượt báo cáo" reportAbuse: "Báo cáo" +reportAbuseRenote: "Báo cáo bài đăng lại" reportAbuseOf: "Báo cáo {name}" fillAbuseReportDescription: "Vui lòng điền thông tin chi tiết về báo cáo này. Nếu đó là về một tút cụ thể, hãy kèm theo URL của tút." abuseReported: "Báo cáo đã được gửi. Cảm ơn bạn nhiều." @@ -716,6 +769,7 @@ lockedAccountInfo: "Ghi chú của bạn sẽ hiển thị với bất kỳ ai, alwaysMarkSensitive: "Luôn đánh dấu NSFW" loadRawImages: "Tải ảnh gốc thay vì ảnh thu nhỏ" disableShowingAnimatedImages: "Không phát ảnh động" +highlightSensitiveMedia: "Đánh dấu nội dung nhạy cảm" verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết đính kèm để hoàn tất xác minh." notSet: "Chưa đặt" emailVerified: "Email đã được xác minh" @@ -731,7 +785,6 @@ thisIsExperimentalFeature: "Tính năng này đang trong quá trình thử nghi developer: "Nhà phát triển" makeExplorable: "Không hiện tôi trong \"Khám phá\"" makeExplorableDescription: "Nếu bạn tắt, tài khoản của bạn sẽ không hiện trong mục \"Khám phá\"." -showGapBetweenNotesInTimeline: "Hiện dải phân cách giữa các tút trên bảng tin" duplicate: "Tạo bản sao" left: "Bên trái" center: "Giữa" @@ -809,6 +862,7 @@ administration: "Quản lý" accounts: "Tài khoản của bạn" switch: "Chuyển đổi" noMaintainerInformationWarning: "Chưa thiết lập thông tin vận hành." +noInquiryUrlWarning: "Địa chỉ hỏi đáp chưa được đặt" noBotProtectionWarning: "Bảo vệ Bot chưa thiết lập." configure: "Thiết lập" postToGallery: "Tạo tút có ảnh" @@ -873,6 +927,7 @@ followersVisibility: "Hiển thị người theo dõi" continueThread: "Tiếp tục xem chuỗi tút" deleteAccountConfirm: "Điều này sẽ khiến tài khoản bị xóa vĩnh viễn. Vẫn tiếp tục?" incorrectPassword: "Sai mật khẩu." +incorrectTotp: "Mã OTP không đúng hoặc đã quá hạn" voteConfirm: "Xác nhận bình chọn \"{choice}\"?" hide: "Ẩn" useDrawerReactionPickerForMobile: "Hiện bộ chọn biểu cảm dạng xổ ra trên điện thoại" @@ -897,6 +952,9 @@ oneHour: "1 giờ" oneDay: "1 ngày" oneWeek: "1 tuần" oneMonth: "1 tháng" +threeMonths: "3 tháng" +oneYear: "1 năm" +threeDays: "3 ngày " reflectMayTakeTime: "Có thể mất một thời gian để điều này được áp dụng." failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản" rateLimitExceeded: "Giới hạn quá mức" @@ -921,6 +979,7 @@ document: "Tài liệu" numberOfPageCache: "Số lượng trang bộ nhớ đệm" numberOfPageCacheDescription: "Việc tăng con số này sẽ cải thiện sự thuận tiện cho người dùng nhưng gây ra nhiều áp lực hơn cho máy chủ cũng như sử dụng nhiều bộ nhớ hơn." logoutConfirm: "Bạn có chắc muốn đăng xuất?" +logoutWillClearClientData: "Đăng xuất sẽ xoá các thiết lập của bạn khỏi trình duyệt. Để có thể khôi phục thiết lập khi đăng nhập lại, bạn phải bật tự động sao lưu cài đặt." lastActiveDate: "Lần cuối vào" statusbar: "Thanh trạng thái" pleaseSelect: "Chọn một lựa chọn" @@ -970,6 +1029,7 @@ neverShow: "Không hiển thị nữa" remindMeLater: "Để sau" didYouLikeMisskey: "Bạn có ưa thích Mískey không?" pleaseDonate: "Misskey là phần mềm miễn phí mà {host} đang sử dụng. Xin mong bạn quyên góp cho chúng tôi để chúng tôi có thể tiếp tục phát triển dịch vụ này. Xin cảm ơn!!" +correspondingSourceIsAvailable: "Mã nguồn có thể được xem tại {anchor}" roles: "Vai trò" role: "Vai trò" noRole: "Bạn chưa được cấp quyền." @@ -997,23 +1057,41 @@ thisPostMayBeAnnoyingHome: "Đăng trên trang chính" thisPostMayBeAnnoyingCancel: "Từ chối" thisPostMayBeAnnoyingIgnore: "Đăng bài để nguyên" collapseRenotes: "Không hiển thị bài viết đã từng xem" +collapseRenotesDescription: "Các bài đăng bị thu gọn mà bạn đã phản hồi hoặc đăng lại trước đây." internalServerError: "Lỗi trong chủ máy" internalServerErrorDescription: "Trong chủ máy lỗi bất ngờ xảy ra" copyErrorInfo: "Sao chép thông tin lỗi" joinThisServer: "Đăng ký trên chủ máy này" exploreOtherServers: "Tìm chủ máy khác" letsLookAtTimeline: "Thử xem Timeline" +disableFederationConfirm: "Bạn có muốn làm điều đó mà không cần liên minh không?" +disableFederationConfirmWarn: "Ngay cả khi bị trì hoãn, bài đăng vẫn sẽ tiếp tục là công khai trừ khi được thiết lập khác. Bạn thường không cần phải làm điều này." disableFederationOk: "Vô hiệu hoá" +invitationRequiredToRegister: "Phiên bản này chỉ dành cho người được mời. Bạn phải nhập mã mời hợp lệ để đăng ký." emailNotSupported: "Máy chủ này không hỗ trợ gửi email" postToTheChannel: "Đăng lên kênh" cannotBeChangedLater: "Không thể thay đổi sau này." +reactionAcceptance: "Phản ứng chấp nhận" likeOnly: "Chỉ lượt thích" +likeOnlyForRemote: "Tất cả (chỉ bao gồm lượt thích trên các máy chủ khác)" +nonSensitiveOnly: "Chỉ nội dung không nhạy cảm" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Chỉ nội dung không nhạy cảm (chỉ bao gồm lượt thích từ máy chủ khác)" rolesAssignedToMe: "Vai trò được giao cho tôi" resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?" sensitiveWords: "Các từ nhạy cảm" +sensitiveWordsDescription: "Phạm vi của tất cả bài đăng chứa các từ được cấu hình sẽ tự động được đặt về \"Home\". Ban có thể thêm nhiều từ trên mỗi dòng." +sensitiveWordsDescription2: "Sử dụng dấu cách sẽ tạo cấu trúc AND và thêm dấu gạch xuôi để sử dụng như một regex." prohibitedWords: "Các từ bị cấm" +prohibitedWordsDescription: "Hiển thị lỗi khi đăng một bài đăng chứa các từ sau. Nhiều từ có thể được thêm bằng cách viết một từ trên mỗi dòng." +prohibitedWordsDescription2: "Sử dụng dấu cách sẽ tạo cấu trúc AND và thêm dấu gạch xuôi để sử dụng như một regex." +hiddenTags: "Hashtag ẩn" +hiddenTagsDescription: "Các hashtag này sẽ không được hiển thị trên danh sách Trending. Nhiều tag có thể được thêm bằng cách viết một tag trên mỗi dòng." +notesSearchNotAvailable: "Tìm kiếm bài đăng hiện không khả dụng." license: "Giấy phép" unfavoriteConfirm: "Bạn thực sự muốn xoá khỏi mục yêu thích?" +myClips: "Các clip của tôi" +drivecleaner: "Trình dọn đĩa" +retryAllQueuesNow: "Thử lại cho tất cả hàng chờ" retryAllQueuesConfirmTitle: "Bạn có muốn thử lại?" retryAllQueuesConfirmText: "Điều này sẽ tạm thời làm tăng mức độ tải của máy chủ." enableChartsForRemoteUser: "Tạo biểu đồ người dùng từ xa" @@ -1049,6 +1127,8 @@ options: "Tùy chọn" specifyUser: "Người dùng chỉ định" failedToPreviewUrl: "Không thể xem trước" update: "Cập nhật" +cancelReactionConfirm: "Bạn có muốn hủy phản ứng của mình không?" +changeReactionConfirm: "Bạn có muốn thay đổi phản ứng của mình không?" later: "Để sau" goToMisskey: "Tới Misskey" installed: "Đã tải xuống" @@ -1097,8 +1177,12 @@ mutualFollow: "Theo dõi lẫn nhau" followingOrFollower: "Đang theo dõi hoặc người theo dõi" externalServices: "Các dịch vụ bên ngoài" sourceCode: "Mã nguồn" +sourceCodeIsNotYetProvided: "Mã nguồn hiện chưa có sẵn, vui lòng liên hệ với quản trị viên để khắc phục sự cố này." +repositoryUrlDescription: "Nếu bạn có kho lưu trữ mã nguồn có thể truy cập công khai, hãy nhập URL. Nếu bạn đang sử dụng Misskey theo mặc định (không thực hiện bất kỳ thay đổi nào đối với mã nguồn), hãy nhập https://github.com/misskey-dev/misskey." feedback: "Phản hồi" feedbackUrl: "URL phản hồi" +impressum: "Thông tin nhà điều hành" +impressumUrl: "URL thông tin nhà điều hành" privacyPolicy: "Chính sách bảo mật" privacyPolicyUrl: "URL Chính sách bảo mật" tosAndPrivacyPolicy: "Điều khoản sử dụng và Chính sách bảo mật" @@ -1113,16 +1197,52 @@ releaseToRefresh: "Thả để làm mới" refreshing: "Đang làm mới" pullDownToRefresh: "Kéo xuống để làm mới" cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích." +decorate: "Trang trí" lastNDays: "{n} ngày trước" +userSaysSomethingSensitive: "Bài đăng có chứa các tập tin nhạy cảm từ {name}" surrender: "Từ chối" +signinWithPasskey: "Đăng nhập bằng mật khẩu của bạn" +passkeyVerificationFailed: "Xác minh mật khẩu không thành công." +messageToFollower: "Tin nhắn cho người theo dõi" +yourNameContainsProhibitedWords: "Tên bạn đang cố gắng đổi có chứa chuỗi ký tự bị cấm." +yourNameContainsProhibitedWordsDescription: "Tên có chứa chuỗi ký tự bị cấm. Nếu bạn muốn sử dụng tên này, hãy liên hệ với quản trị viên máy chủ của bạn." +pleaseSelectAccount: "Chọn tài khoản của bạn" +federationDisabled: "Liên kết bị vô hiệu hóa trên máy chủ này. Bạn không thể tương tác với người dùng trên các máy chủ khác." +reactAreYouSure: "Bạn có muốn phản hồi với \" {emoji} \" không?" +preferences: "Thiết lập môi trường" +accessibility: "Khả năng tiếp cận" +preferencesProfile: "Hồ sơ sở thích" +preferenceSyncConflictTitle: "Cài đặt tồn tại trên máy chủ" +preferenceSyncConflictText: "Các thiết lập đồng bộ hóa được bật sẽ lưu các giá trị của chúng vào máy chủ. Tuy nhiên, có những giá trị hiện có trên máy chủ. Bạn muốn ghi đè lên bộ giá trị nào?" +paste: "dán" postForm: "Mẫu đăng" information: "Giới thiệu" +chat: "Trò chuyện" +migrateOldSettings: "Di chuyển cài đặt cũ" +migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn." +inMinutes: "phút" +inDays: "ngày" +widgets: "Tiện ích" +presets: "Mẫu thiết lập" +_imageEditing: + _vars: + filename: "Tên tập tin" +_imageFrameEditor: + header: "Ảnh bìa" + font: "Phông chữ" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" _chat: invitations: "Mời" noHistory: "Không có dữ liệu" members: "Thành viên" home: "Trang chính" send: "Gửi" +_settings: + preferencesBanner: "Bạn có thể cấu hình hành vi chung của máy khách theo sở thích của mình." +_accountSettings: + requireSigninToViewContents: "Yêu cầu đăng nhập để xem nội dung" + requireSigninToViewContentsDescription1: "Yêu cầu đăng nhập để xem tất cả ghi chú và nội dung khác mà bạn tạo. Điều này được kỳ vọng sẽ có hiệu quả trong việc ngăn chặn thông tin bị thu thập bởi các trình thu thập thông tin." _delivery: stop: "Đã vô hiệu hóa" _type: @@ -1146,8 +1266,33 @@ _initialAccountSetting: pushNotificationDescription: "Bật thông báo đẩy sẽ cho phép bạn nhận thông báo từ {name} trực tiếp từ thiết bị của bạn." initialAccountSettingCompleted: "Thiết lập tài khoản thành công!" haveFun: "Hãy tận hưởng {name} nhé!" + youCanContinueTutorial: "Bạn có thể tiếp tục xem hướng dẫn về cách sử dụng {name} (Misskey) hoặc bạn có thể thoát khỏi phần thiết lập tại đây và bắt đầu sử dụng ngay lập tức." + startTutorial: "Bắt đầu hướng dẫn" skipAreYouSure: "Bạn thực sự muốn bỏ qua mục thiết lập tài khoản?" laterAreYouSure: "Bạn thực sự muốn thiết lập tài khoản vào lúc khác?" +_initialTutorial: + launchTutorial: "Bắt đầu hướng dẫn" + title: "Hướng dẫn" + wellDone: "Làm tốt!" + skipAreYouSure: "Thoát khỏi hướng dẫn?" + _landing: + title: "Chào mừng đến với Hướng dẫn" + description: "Tại đây, bạn có thể tìm hiểu những điều cơ bản về cách sử dụng Misskey và các tính năng của nó." + _note: + title: "Bài Viết là gì?" + description: "Các bài đăng trên Misskey được gọi là 'Bài Viết'. Ghi chú được sắp xếp theo thứ tự thời gian trên dòng thời gian và được cập nhật theo thời gian thực." + _timeline: + home: "Bạn có thể xem ghi chú từ những tài khoản bạn theo dõi." + local: "Bạn có thể xem ghi chú từ tất cả người dùng trên máy chủ này." + social: "Ghi chú từ dòng thời gian Trang chủ và Địa phương sẽ được hiển thị." + global: "Bạn có thể xem ghi chú từ tất cả các máy chủ được kết nối." + _postNote: + _visibility: + home: "Chỉ công khai trên dòng thời gian Trang chủ. Những người truy cập trang cá nhân của bạn, thông qua người theo dõi và thông qua ghi chú lại có thể thấy thông tin đó." +_timelineDescription: + home: "Trong dòng thời gian Trang chính, bạn có thể xem ghi chú từ các tài khoản bạn theo dõi." + local: "Trong dòng thời gian cục bộ, bạn có thể xem ghi chú từ tất cả người dùng trên máy chủ này." + social: "Dòng thời gian Xã hội hiển thị các ghi chú từ cả dòng thời gian Trang chủ và Địa phương." _serverSettings: iconUrl: "Biểu tượng URL" appIconResolutionMustBe: "Độ phân giải tối thiểu là {resolution}." @@ -1308,7 +1453,7 @@ _achievements: _postedAt0min0sec: title: "Tín hiệu báo giờ" description: "Đăng bài vào 0 phút 0 giây" - flavor: "Piiiiiii ĐÂY LÀ TIẾNG NÓI VIỆT NAM" + flavor: "Pin pop pop pop" _selfQuote: title: "Nói đến bản thân" description: "Trích dẫn bài viết của mình" @@ -1551,7 +1696,6 @@ _theme: buttonBg: "Nền nút" buttonHoverBg: "Nền nút (Chạm)" inputBorder: "Đường viền khung soạn thảo" - driveFolderBg: "Nền thư mục Ổ đĩa" badge: "Huy hiệu" messageBg: "Nền chat" fgHighlighted: "Chữ nổi bật" @@ -1682,6 +1826,14 @@ _widgets: _userList: chooseList: "Chọn danh sách" clicker: "clicker" +_widgetOptions: + height: "Chiều cao" + _button: + colored: "Với màu" + _clock: + size: "Kích thước" + _birthdayFollowings: + period: "Thời hạn" _cw: hide: "Ẩn" show: "Tải thêm" @@ -1724,6 +1876,9 @@ _postForm: replyPlaceholder: "Trả lời tút này" quotePlaceholder: "Trích dẫn tút này" channelPlaceholder: "Đăng lên một kênh" + _howToUse: + visibility_title: "Hiển thị" + menu_title: "Menu" _placeholders: a: "Bạn đang định làm gì?" b: "Hôm nay bạn có gì vui?" @@ -1923,11 +2078,21 @@ _abuseReport: _recipientType: mail: "Email" _moderationLogTypes: + createRole: "Tạo một vai trò" + deleteRole: "Xóa vai trò" + updateRole: "Cập nhật vai trò" + assignRole: "Chỉ định cho vai trò" + unassignRole: "Bỏ gán vai trò" suspend: "Vô hiệu hóa" + unsuspend: "Rã đông" resetPassword: "Đặt lại mật khẩu" createInvitation: "Tạo lời mời" _reversi: total: "Tổng cộng" +_customEmojisManager: + _local: + _list: + confirmDeleteEmojisDescription: "Xóa các biểu tượng cảm xúc {count} đã chọn. Bạn có muốn chạy nó không?" _remoteLookupErrors: _noSuchObject: title: "Không tìm thấy" @@ -1935,3 +2100,24 @@ _search: searchScopeAll: "Tất cả" searchScopeLocal: "Máy chủ này" searchScopeUser: "Người dùng chỉ định" +_watermarkEditor: + opacity: "Độ trong suốt" + scale: "Kích thước" + text: "Văn bản" + position: "Vị trí" + type: "Loại" + image: "Hình ảnh" + advanced: "Nâng cao" + angle: "Góc" +_imageEffector: + _fxProps: + angle: "Góc" + scale: "Kích thước" + size: "Kích thước" + offset: "Vị trí" + color: "Màu sắc" + opacity: "Độ trong suốt" + lightness: "Độ sáng" +_qr: + showTabTitle: "Hiển thị" + raw: "Văn bản" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 4b78b0a362..875ffe2b43 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -10,7 +10,7 @@ notifications: "通知" username: "用户名" password: "密码" initialPasswordForSetup: "初始化密码" -initialPasswordIsIncorrect: "初始化密码不正确" +initialPasswordIsIncorrect: "初始化密码不正确。" initialPasswordForSetupDescription: "如果是自己安装的 Misskey,请输入配置文件里设好的密码。\n如果使用的是 Misskey 的托管服务等,请输入服务商提供的密码。\n如果没有设置密码,请留空并继续。" forgotPassword: "忘记密码" fetchingAsApObject: "在联邦宇宙查询中..." @@ -53,10 +53,10 @@ copyRemoteLink: "复制远程链接" copyLinkRenote: "复制转帖链接" delete: "删除" deleteAndEdit: "删除并编辑" -deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。" +deleteAndEditConfirm: "要删除此帖并再次编辑吗?此帖下所有的回应、转发和回复也将被删除。" addToList: "添加至列表" addToAntenna: "添加到天线" -sendMessage: "发送" +sendMessage: "发送消息" copyRSS: "复制RSS" copyUsername: "复制用户名" copyUserId: "复制用户 ID" @@ -83,11 +83,13 @@ files: "文件" download: "下载" driveFileDeleteConfirm: "要删除「{name}」文件吗?附加此文件的帖子也会被删除。" unfollowConfirm: "要取消对 {name} 的关注吗?" +cancelFollowRequestConfirm: "要取消申请关注{name}吗?" +rejectFollowRequestConfirm: "要拒绝{name}的关注申请吗?" exportRequested: "导出请求已提交,这可能需要花一些时间,导出的文件将保存到网盘中。" importRequested: "导入请求已提交,这可能需要花一点时间。" lists: "列表" noLists: "列表为空" -note: "帖子" +note: "发帖" notes: "帖子" following: "关注中" followers: "关注者" @@ -106,8 +108,8 @@ privacy: "隐私" makeFollowManuallyApprove: "关注请求需要批准" defaultNoteVisibility: "默认可见性" follow: "关注" -followRequest: "关注申请" -followRequests: "关注申请" +followRequest: "申请关注" +followRequests: "关注请求" unfollow: "取消关注" followRequestPending: "关注请求待批准" enterEmoji: "输入表情符号" @@ -134,7 +136,7 @@ emojiPicker: "表情符号选择器" pinnedEmojisForReactionSettingDescription: "可以设置发表回应时置顶显示的表情符号" pinnedEmojisSettingDescription: "可以设置输入表情符号时置顶显示的表情符号" emojiPickerDisplay: "选择器显示设置" -overwriteFromPinnedEmojisForReaction: "从「置顶(回应)」设置覆盖" +overwriteFromPinnedEmojisForReaction: "使用「置顶(回应)」设置覆盖" overwriteFromPinnedEmojis: "从全局设置覆盖" reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。" rememberNoteVisibility: "保存上次设置的可见性" @@ -146,7 +148,7 @@ enterFileName: "输入文件名" mute: "屏蔽" unmute: "取消隐藏" renoteMute: "隐藏转帖" -renoteUnmute: "解除隐藏转帖" +renoteUnmute: "取消隐藏转帖" block: "屏蔽" unblock: "取消屏蔽" suspend: "冻结" @@ -172,7 +174,7 @@ emojiUrl: "emoji 地址" addEmoji: "添加表情符号" settingGuide: "推荐配置" cacheRemoteFiles: "缓存远程文件" -cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。" +cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。" youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。" cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件" cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。" @@ -182,7 +184,7 @@ flagAsCat: "喵!!!!!!!!!!!!" flagAsCatDescription: "喵喵喵??" flagShowTimelineReplies: "在时间线上显示帖子的回复" flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。" -autoAcceptFollowed: "自动允许来自我关注的用户对我的关注请求" +autoAcceptFollowed: "自动允许回关请求" addAccount: "添加账户" reloadAccountsList: "更新账户列表" loginFailed: "登录失败" @@ -220,6 +222,7 @@ silenceThisInstance: "静音此服务器" mediaSilenceThisInstance: "隐藏此服务器的媒体文件" operations: "操作" software: "软件" +softwareName: "软件名" version: "版本" metadata: "元数据" withNFiles: "{n} 个文件" @@ -238,21 +241,21 @@ clearCachedFilesConfirm: "确定要清除所有缓存的远程文件吗?" blockedInstances: "被屏蔽的服务器" blockedInstancesDescription: "设定要屏蔽的服务器,以换行分隔。被屏蔽的服务器将无法与本服务器进行交换通讯。子域名也同样会被屏蔽。" silencedInstances: "被静音的服务器" -silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户将默认处于「静音」状态,仅能发送关注请求,并且在未关注状态下无法提及本地账户。被阻止的实例不受影响。" +silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户都被视为「静音」状态,且关注操作均需要被批准。已被屏蔽的实例不受影响。" mediaSilencedInstances: "已隐藏媒体文件的服务器" -mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置为隐藏媒体文件服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。被阻止的实例不受影响。" +mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置的服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。已被屏蔽的实例不受影响。" federationAllowedHosts: "允许联合的服务器" federationAllowedHostsDescription: "设定允许联合的服务器,以换行分隔。" -muteAndBlock: "隐藏和屏蔽" -mutedUsers: "已隐藏用户" +muteAndBlock: "隐藏/屏蔽" +mutedUsers: "已隐藏的用户" blockedUsers: "已屏蔽的用户" noUsers: "无用户" editProfile: "编辑资料" noteDeleteConfirm: "确定要删除该帖子吗?" pinLimitExceeded: "无法置顶更多了" -intro: "Misskey 的部署结束啦!创建管理员账号吧!" done: "完成" processing: "正在处理" +preprocessing: "准备中" preview: "预览" default: "默认" defaultValueIs: "默认值: {value}" @@ -298,11 +301,13 @@ uploadFromUrl: "从网址上传" uploadFromUrlDescription: "输入文件的 URL" uploadFromUrlRequested: "请求上传" uploadFromUrlMayTakeTime: "上传可能需要一些时间完成。" +uploadNFiles: "上传 {n} 个文件" explore: "发现" messageRead: "已读" +readAllChatMessages: "将所有消息标记为已读" noMoreHistory: "没有更多的历史记录" startChat: "开始聊天" -nUsersRead: "{n} 人已读" +nUsersRead: "{n}人已读" agreeTo: "勾选则表示已阅读并同意 {0}" agree: "同意" agreeBelow: "同意以下内容" @@ -326,11 +331,13 @@ dark: "深色" lightThemes: "浅色主题" darkThemes: "深色主题" syncDeviceDarkMode: "将深色模式与设备设置同步" +switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」已开启。要关闭同步并手动切换模式吗?" drive: "网盘" fileName: "文件名称" selectFile: "选择文件" selectFiles: "选择文件" selectFolder: "选择文件夹" +unselectFolder: "取消全选文件夹" selectFolders: "选择多个文件夹" fileNotSelected: "未选择文件" renameFile: "重命名文件" @@ -343,6 +350,7 @@ addFile: "添加文件" showFile: "显示文件" emptyDrive: "网盘中无文件" emptyFolder: "此文件夹中无文件" +dropHereToUpload: "将文件拖动到这里来上传" unableToDelete: "无法删除" inputNewFileName: "请输入新文件名" inputNewDescription: "请输入新标题" @@ -393,7 +401,7 @@ basicInfo: "基本信息" pinnedUsers: "置顶用户" pinnedUsersDescription: "输入您想要固定到“发现”页面的用户,一行一个。" pinnedPages: "固定页面" -pinnedPagesDescription: "输入您要固定到服务器首页的页面路径,一行一个。" +pinnedPagesDescription: "输入您要固定到服务器首页的页面路径,以换行符分隔。" pinnedClipId: "置顶的便签 ID" pinnedNotes: "已置顶的帖子" hcaptcha: "hCaptcha" @@ -426,7 +434,7 @@ notifyAntenna: "开启通知" withFileAntenna: "仅带有附件的帖子" excludeNotesInSensitiveChannel: "排除敏感频道内的帖子" enableServiceworker: "启用 ServiceWorker" -antennaUsersDescription: "指定用户名,一行一个" +antennaUsersDescription: "指定用户名,用换行符进行分隔" caseSensitive: "区分大小写" withReplies: "包括回复" connectedTo: "您的账号已连到接以下第三方账号" @@ -458,7 +466,7 @@ moderationNote: "管理笔记" moderationNoteDescription: "可以用来记录仅在管理员之间共享的笔记。" addModerationNote: "添加管理笔记" moderationLogs: "管理日志" -nUsersMentioned: "{n} 被提到" +nUsersMentioned: "{n}人投稿" securityKeyAndPasskey: "安全密钥或 Passkey" securityKey: "安全密钥" lastUsed: "最后使用:" @@ -468,14 +476,14 @@ passwordLessLogin: "无密码登录" passwordLessLoginDescription: "不使用密码,仅使用安全密钥或 Passkey 登录" resetPassword: "重置密码" newPasswordIs: "新的密码是「{password}」" -reduceUiAnimation: "减少UI动画" +reduceUiAnimation: "减少 UI 动画" share: "分享" notFound: "未找到" notFoundDescription: "没有与指定 URL 对应的页面。" uploadFolder: "默认上传文件夹" markAsReadAllNotifications: "将所有通知标为已读" markAsReadAllUnreadNotes: "将所有帖子标记为已读" -markAsReadAllTalkMessages: "将所有聊天标记为已读" +markAsReadAllTalkMessages: "将所有私信标记为已读" help: "帮助" inputMessageHere: "在此键入信息" close: "关闭" @@ -535,7 +543,7 @@ regenerate: "重新生成" fontSize: "字体大小" mediaListWithOneImageAppearance: "仅一张图片的媒体列表高度" limitTo: "上限为 {x}" -noFollowRequests: "没有关注申请" +noFollowRequests: "没有关注请求" openImageInNewTab: "在新标签页中打开图片" dashboard: "管理面板" local: "本地" @@ -575,8 +583,10 @@ showFixedPostForm: "在时间线顶部显示发帖框" showFixedPostFormInChannel: "在时间线顶部显示发帖对话框(频道)" withRepliesByDefaultForNewlyFollowed: "在时间线中默认包含新关注用户的回复" newNoteRecived: "有新的帖子" +newNote: "新帖子" sounds: "提示音" sound: "提示音" +notificationSoundSettings: "设置通知声音" listen: "试听" none: "无" showInPage: "在页面中显示" @@ -593,7 +603,7 @@ recentUsed: "最近使用" install: "安装" uninstall: "卸载" installedApps: "已授权的应用" -nothing: "没有" +nothing: "无" installedDate: "授权日期" lastUsedDate: "最近使用" state: "状态" @@ -683,13 +693,13 @@ emptyToDisableSmtpAuth: "用户名和密码留空可以禁用 SMTP 验证" smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS" smtpSecureInfo: "使用 STARTTLS 时关闭。" testEmail: "邮件发送测试" -wordMute: "隐藏关键词" +wordMute: "折叠关键词" wordMuteDescription: "折叠包含指定关键词的帖子。被折叠的帖子可单击展开。" -hardWordMute: "隐藏硬关键词" -showMutedWord: "显示已隐藏的关键词" -hardWordMuteDescription: "隐藏包含指定关键词的帖子。与隐藏关键词不同,帖子将完全不会显示。" +hardWordMute: "屏蔽关键词" +showMutedWord: "显示已折叠的关键词" +hardWordMuteDescription: "屏蔽包含指定关键词的帖子。与折叠关键词不同,帖子将完全不会显示。" regexpError: "正则表达式错误" -regexpErrorDescription: "{tab} 隐藏文字的第 {line} 行的正则表达式有错误:" +regexpErrorDescription: "{tab} 折叠关键词的第 {line} 行的正则表达式有错误:" instanceMute: "已隐藏的服务器" userSaysSomething: "{name} 说了什么,但是被屏蔽词过滤了" userSaysSomethingAbout: "{name} 说了关于「{word}」的什么" @@ -768,6 +778,7 @@ lockedAccountInfo: "即使启用该功能,只要帖子可见范围不是「仅 alwaysMarkSensitive: "默认将媒体文件标记为敏感内容" loadRawImages: "添加附件图像的缩略图时使用原始图像质量" disableShowingAnimatedImages: "不播放动画" +disableShowingAnimatedImages_caption: "如果即使关闭了此设置但动画仍无法播放,则可能是浏览器或操作系统的辅助功能设置,又或者是省电设置等产生了干扰。" highlightSensitiveMedia: "高亮显示敏感媒体" verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。" notSet: "未设置" @@ -775,7 +786,7 @@ emailVerified: "电子邮件地址已验证" noteFavoritesCount: "收藏的帖子数" pageLikesCount: "页面点赞次数" pageLikedCount: "页面被点赞次数" -contact: "联系人" +contact: "联系方式" useSystemFont: "使用系统默认字体" clips: "便签" experimentalFeatures: "实验性功能" @@ -784,19 +795,19 @@ thisIsExperimentalFeature: "这是一项实验性功能。规范可能会变更 developer: "开发者" makeExplorable: "使账号可见。" makeExplorableDescription: "关闭时,账号不会显示在\"发现\"中。" -showGapBetweenNotesInTimeline: "时间线上的帖子分开显示。" duplicate: "复制" left: "左" -center: "中央" +center: "居中" wide: "宽" narrow: "窄" reloadToApplySetting: "页面刷新后设置才会生效。是否现在刷新页面?" needReloadToApply: "重新载入后应用才会生效。" +needToRestartServerToApply: "需要重启服务才能应用更改。" showTitlebar: "显示标题栏" clearCache: "清除缓存" onlineUsersCount: "{n} 人在线" nUsers: "{n} 用户" -nNotes: "{n} 帖子" +nNotes: "{n}帖子" sendErrorReports: "发送错误报告" sendErrorReportsDescription: "启用后,如果出现问题,可以与 Misskey 共享详细的错误信息,从而帮助提高软件的质量。错误信息包括操作系统版本、浏览器类型、行为历史记录等。" myTheme: "我的主题" @@ -808,7 +819,7 @@ advanced: "高级" advancedSettings: "高级设置" value: "值" createdAt: "创建日期" -updatedAt: "更新时间" +updatedAt: "更新日期" saveConfirm: "确定保存?" deleteConfirm: "确定删除?" invalidValue: "无效值。" @@ -820,7 +831,7 @@ youAreRunningUpToDateClient: "您所使用的客户端已经是最新的。" newVersionOfClientAvailable: "新版本的客户端可用。" usageAmount: "使用量" capacity: "容量" -inUse: "已使用" +inUse: "使用中" editCode: "编辑代码" apply: "应用" receiveAnnouncementFromInstance: "从服务器接收通知" @@ -865,12 +876,12 @@ noMaintainerInformationWarning: "尚未设置管理员信息。" noInquiryUrlWarning: "尚未设置联络地址。" noBotProtectionWarning: "尚未设置 Bot 防御。" configure: "设置" -postToGallery: "发送到图库" -postToHashtag: "投稿到这个标签" -gallery: "图库" +postToGallery: "创建新图集" +postToHashtag: "发布至该话题" +gallery: "图集" recentPosts: "最新发布" popularPosts: "热门投稿" -shareWithNote: "在帖子中分享" +shareWithNote: "分享到帖文" ads: "广告" expiration: "截止时间" startingperiod: "开始时间" @@ -881,7 +892,7 @@ middle: "中" low: "低" emailNotConfiguredWarning: "尚未设置电子邮件地址。" ratio: "比率" -previewNoteText: "预览文本" +previewNoteText: "预览正文" customCss: "自定义 CSS" customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用。" global: "全局" @@ -901,7 +912,7 @@ accountDeletionInProgress: "正在删除账户" usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。" aiChanMode: "小蓝模式" devMode: "开发者模式" -keepCw: "回复时维持隐藏内容" +keepCw: "始终开启内容警告" pubSub: "Pub/Sub 账户" lastCommunication: "最近通信" resolved: "已解决" @@ -920,8 +931,8 @@ manageAccounts: "管理账户" makeReactionsPublic: "将回应设置为公开" makeReactionsPublicDescription: "将您发表过的回应设置成公开可见。" classic: "经典" -muteThread: "隐藏帖子列表" -unmuteThread: "取消隐藏帖子列表" +muteThread: "静音帖文串" +unmuteThread: "取消帖文串静音" followingVisibility: "关注的人的公开范围" followersVisibility: "关注者的公开范围" continueThread: "查看更多帖子" @@ -944,17 +955,17 @@ searchByGoogle: "Google" instanceDefaultLightTheme: "服务器默认浅色主题" instanceDefaultDarkTheme: "服务器默认深色主题" instanceDefaultThemeDescription: "以对象格式输入主题代码" -mutePeriod: "隐藏期限" +mutePeriod: "隐藏时长" period: "截止时间" indefinitely: "永久" -tenMinutes: "10 分钟" +tenMinutes: "10分钟" oneHour: "1 小时" -oneDay: "1 天" +oneDay: "1天" oneWeek: "1 周" -oneMonth: "1 个月" -threeMonths: "3 个月" +oneMonth: "1个月" +threeMonths: "3个月" oneYear: "1 年" -threeDays: "3 天" +threeDays: "3天" reflectMayTakeTime: "可能需要一些时间才能体现出效果。" failedToFetchAccountInformation: "获取账户信息失败" rateLimitExceeded: "已超过速率限制" @@ -963,8 +974,8 @@ cropImageAsk: "是否要裁剪图像?" cropYes: "去裁剪" cropNo: "就这样吧!" file: "文件" -recentNHours: "最近 {n} 小时" -recentNDays: "最近 {n} 天" +recentNHours: "最近{n}小时" +recentNDays: "最近{n}天" noEmailServerWarning: "电子邮件服务器未设置。" thereIsUnresolvedAbuseReportWarning: "有未解决的报告" recommended: "推荐" @@ -998,6 +1009,7 @@ failedToUpload: "上传失败" cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法上传。" cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。" cannotUploadBecauseExceedsFileSizeLimit: "无法上传文件,超过文件大小限制。" +cannotUploadBecauseUnallowedFileType: "因文件类型被禁止而无法上传。" beta: "测试" enableAutoSensitive: "自动 NSFW 识别" enableAutoSensitiveDescription: "使用机器学习在可用时自动使用 NSFW 标记来标记媒体。即使您关闭此功能,根据服务器的不同,它仍然可能会自动设置。" @@ -1013,6 +1025,9 @@ pushNotificationAlreadySubscribed: "推送通知消息已启用" pushNotificationNotSupported: "浏览器或服务器不支持推送通知消息" sendPushNotificationReadMessage: "删除已读推送通知消息" sendPushNotificationReadMessageCaption: "您终端设备的电池消耗可能会增加。" +pleaseAllowPushNotification: "请在浏览器中启用推送通知" +browserPushNotificationDisabled: "未能获取发送通知的权限" +browserPushNotificationDisabledDescription: "{serverName}无权限发送通知。请在浏览器设置中允许通知后重新尝试。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "还原" @@ -1022,7 +1037,7 @@ tools: "工具" cannotLoad: "无法加载" numberOfProfileView: "个人资料展示次数" like: "点赞!" -unlike: "取消赞" +unlike: "取消喜欢" numberOfLikes: "点赞数" show: "显示" neverShow: "不再显示" @@ -1049,6 +1064,7 @@ permissionDeniedError: "操作被拒绝" permissionDeniedErrorDescription: "本账户没有执行该操作的权限。" preset: "预设值" selectFromPresets: "从预设值中选择" +custom: "自定义" achievements: "成就" gotInvalidResponseError: "服务器无应答" gotInvalidResponseErrorDescription: "您的网络连接可能出现了问题, 或是远程服务器暂时不可用. 请稍后重试。" @@ -1057,7 +1073,7 @@ thisPostMayBeAnnoyingHome: "发到首页" thisPostMayBeAnnoyingCancel: "取消" thisPostMayBeAnnoyingIgnore: "就这样发布" collapseRenotes: "省略显示已经看过的转发内容" -collapseRenotesDescription: "将回应过或转贴过的贴子折叠表示。" +collapseRenotesDescription: "折叠显示回应或转发过的帖文。" internalServerError: "内部服务器错误" internalServerErrorDescription: "内部服务器发生了预期外的错误" copyErrorInfo: "复制错误信息" @@ -1073,7 +1089,7 @@ postToTheChannel: "发布到频道" cannotBeChangedLater: "之后不能再更改。" reactionAcceptance: "接受表情回应" likeOnly: "仅点赞" -likeOnlyForRemote: "远程仅点赞" +likeOnlyForRemote: "全部(远程仅点赞)" nonSensitiveOnly: "仅限非敏感内容" nonSensitiveOnlyForLocalLikeOnlyForRemote: "仅限非敏感内容(远程仅点赞)" rolesAssignedToMe: "指派给自己的角色" @@ -1087,6 +1103,7 @@ prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜 hiddenTags: "隐藏标签" hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。" notesSearchNotAvailable: "帖子检索不可用" +usersSearchNotAvailable: "用户检索不可用" license: "许可信息" unfavoriteConfirm: "确定要取消收藏吗?" myClips: "我的便签" @@ -1097,7 +1114,7 @@ retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加" enableChartsForRemoteUser: "生成远程用户的图表" enableChartsForFederatedInstances: "生成远程服务器的图表" enableStatsForFederatedInstances: "获取远程服务器的信息" -showClipButtonInNoteFooter: "在贴文下方显示便签按钮" +showClipButtonInNoteFooter: "在帖文下方显示便签按钮" reactionsDisplaySize: "回应显示大小" limitWidthOfReaction: "限制回应的最大宽度,并将其缩小显示" noteIdOrUrl: "帖子 ID 或 URL" @@ -1135,15 +1152,15 @@ archive: "归档" archived: "已归档" unarchive: "取消归档" channelArchiveConfirmTitle: "要将 {name} 归档吗?" -channelArchiveConfirmDescription: "归档后,在频道列表与搜索结果中不会显示,也无法发布新的贴文。" +channelArchiveConfirmDescription: "归档后,不会在频道列表与搜索结果中显示,也无法发布新的帖文。" thisChannelArchived: "该频道已被归档。" displayOfNote: "显示帖子" -initialAccountSetting: "初始设置" +initialAccountSetting: "初始设定" youFollowing: "正在关注" preventAiLearning: "拒绝接受生成式 AI 的学习" preventAiLearningDescription: "要求文章生成 AI 或图像生成 AI 不能够以发布的帖子和图像等内容作为学习对象。这是通过在 HTML 响应中包含 noai 标志来实现的,这不能完全阻止 AI 学习你的发布内容,并不是所有 AI 都会遵守这类请求。" options: "选项" -specifyUser: "用户指定" +specifyUser: "指定用户" lookupConfirm: "确定查询?" openTagPageConfirm: "确定打开话题标签页面?" specifyHost: "指定主机名" @@ -1161,6 +1178,7 @@ installed: "已安装" branding: "品牌" enableServerMachineStats: "公开服务器硬件统计信息" enableIdenticonGeneration: "启用生成用户 Identicon" +showRoleBadgesOfRemoteUsers: "显示远程用户的角色徽章" turnOffToImprovePerformance: "关闭该选项可以提高性能。" createInviteCode: "生成邀请码" createWithOptions: "使用选项来创建" @@ -1194,7 +1212,7 @@ renotes: "转发" loadReplies: "查看回复" loadConversation: "查看对话" pinnedList: "已置顶的列表" -keepScreenOn: "保持设备屏幕开启" +keepScreenOn: "保持屏幕常亮" verifiedLink: "已验证的链接" notifyNotes: "打开发帖通知" unnotifyNotes: "关闭发帖通知" @@ -1237,9 +1255,8 @@ showAvatarDecorations: "显示头像挂件" releaseToRefresh: "松开以刷新" refreshing: "刷新中" pullDownToRefresh: "下拉以刷新" -disableStreamingTimeline: "禁止实时更新时间线" useGroupedNotifications: "分组显示通知" -signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" +emailVerificationFailedError: "确认电子邮件时出现错误。链接可能已过期。" cwNotationRequired: "在启用「隐藏内容」时必须输入注释" doReaction: "回应" code: "代码" @@ -1259,7 +1276,7 @@ replaying: "重播中" endReplay: "结束回放" copyReplayData: "复制回放数据" ranking: "排行榜" -lastNDays: "最近 {n} 天" +lastNDays: "最近{n}天" backToTitle: "返回标题" hemisphere: "居住地区" withSensitive: "显示包含敏感媒体的帖子" @@ -1276,7 +1293,7 @@ useNativeUIForVideoAudioPlayer: "使用浏览器的 UI 播放动画及音频" keepOriginalFilename: "保持原文件名" keepOriginalFilenameDescription: "若关闭此设置,上传文件时文件名将被替换为随机字符。" noDescription: "没有描述" -alwaysConfirmFollow: "总是确认关注" +alwaysConfirmFollow: "在关注时始终确认" inquiry: "联系我们" tryAgain: "请再试一次" confirmWhenRevealingSensitiveMedia: "显示敏感内容前需要确认" @@ -1309,32 +1326,37 @@ availableRoles: "可用角色" acknowledgeNotesAndEnable: "理解注意事项后再开启。" federationSpecified: "此服务器已开启联合白名单。只能与管理员指定的服务器通信。" federationDisabled: "此服务器已禁用联合。无法与其它服务器上的用户通信。" +draft: "草稿" +draftsAndScheduledNotes: "草稿和定时发送" confirmOnReact: "发送回应前需要确认" reactAreYouSure: "要用「{emoji}」进行回应吗?" markAsSensitiveConfirm: "要将此媒体标记为敏感吗?" unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?" -preferences: "设置" +preferences: "偏好设置" accessibility: "辅助功能" -preferencesProfile: "设置的配置" +preferencesProfile: "设置的配置文件" copyPreferenceId: "复制设置 ID" resetToDefaultValue: "重置为默认值" -overrideByAccount: "用账户覆盖" +overrideByAccount: "覆盖账号" untitled: "未命名" noName: "没有名字" skip: "跳过" restore: "恢复" syncBetweenDevices: "设备间同步" preferenceSyncConflictTitle: "服务器上已存在设定值" -preferenceSyncConflictText: "服务器上已有此设置的设定值。要覆盖哪个设定值?" +preferenceSyncConflictText: "即将保存设定值到服务器,但检测到服务器上已有此设置的设定值。要使用哪个设定值?" +preferenceSyncConflictChoiceMerge: "合并" preferenceSyncConflictChoiceServer: "服务器上的设定值" preferenceSyncConflictChoiceDevice: "设备上的设定值" preferenceSyncConflictChoiceCancel: "取消同步" paste: "粘贴" emojiPalette: "表情符号调色板" -postForm: "投稿窗口" +postForm: "发帖窗口" textCount: "字数" information: "关于" chat: "聊天" +directMessage: "私信" +directMessage_short: "消息" migrateOldSettings: "迁移旧设置信息" migrateOldSettings_description: "通常设置信息将自动迁移。但如果由于某种原因迁移不成功,则可以手动触发迁移过程。当前的配置信息将被覆盖。" compress: "压缩" @@ -1345,40 +1367,135 @@ embed: "嵌入" settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置 → 其它 → 迁移旧设置来手动迁移)" readonly: "只读" goToDeck: "返回至 Deck" +federationJobs: "联合作业" +driveAboutTip: "网盘可以显示以前上传的文件。
\n也可以在发布帖子时重复使用文件,或在发布帖子前预先上传文件。
\n删除文件时,其将从至今为止所有用到该文件的地方(如帖子、页面、头像、横幅)消失。
\n也可以新建文件夹来整理文件。" +scrollToClose: "滑动并关闭" +advice: "建议" +realtimeMode: "实时模式" +turnItOn: "开启" +turnItOff: "关闭" +emojiMute: "打码表情符号" +emojiUnmute: "取消表情符号打码" +muteX: "隐藏{x}" +unmuteX: "取消对{x}的隐藏" +abort: "中止" +tip: "提示和技巧" +redisplayAllTips: "重新显示所有的提示和技巧" +hideAllTips: "隐藏所有的提示和技巧" +defaultImageCompressionLevel: "默认图像压缩等级" +defaultImageCompressionLevel_description: "较低的等级可以保持画质,但会增加文件大小。
较高的等级可以减少文件大小,但相对应的画质将会降低。" +defaultCompressionLevel: "默认压缩等级" +defaultCompressionLevel_description: "较低的等级可以保持质量,但会增加文件大小。
较高的等级可以减少文件大小,但相对应的质量将会降低。" +inMinutes: "分钟" +inDays: "天" +safeModeEnabled: "已启用安全模式" +pluginsAreDisabledBecauseSafeMode: "因启用了安全模式,所有插件均已被禁用。" +customCssIsDisabledBecauseSafeMode: "因启用了安全模式,无法应用自定义 CSS。" +themeIsDefaultBecauseSafeMode: "启用安全模式时将使用默认主题。关闭安全模式后将还原。" +thankYouForTestingBeta: "感谢您协助测试 beta 版!" +createUserSpecifiedNote: "创建指定用户的帖子" +schedulePost: "定时发布" +scheduleToPostOnX: "预定在 {x} 发出" +scheduledToPostOnX: "已预定在 {x} 发出" +schedule: "定时" +scheduled: "定时" +widgets: "小工具" +deviceInfo: "设备信息" +deviceInfoDescription: "咨询技术问题时,将以下信息一并发送有助于解决问题。" +youAreAdmin: "你是管理员" +frame: "边框" +presets: "预设值" +zeroPadding: "填充 0" +nothingToConfigure: "没有项目" +_imageEditing: + _vars: + caption: "文件标题" + filename: "文件名称" + filename_without_ext: "不带扩展名的文件名" + year: "拍摄年" + month: "拍摄月" + day: "拍摄日" + hour: "拍摄时间(时)" + minute: "拍摄时间(分)" + second: "拍摄时间(秒)" + camera_model: "相机名称" + camera_lens_model: "镜头型号" + camera_mm: "焦距" + camera_mm_35: "焦距(35mm等效)" + camera_f: "光圈" + camera_s: "快门速度" + camera_iso: "ISO" + gps_lat: "纬度" + gps_long: "经度" +_imageFrameEditor: + title: "编辑边框" + tip: "您可以通过添加包含边框和元数据的标签来装饰图片。" + header: "顶栏" + footer: "页脚" + borderThickness: "边框宽度" + labelThickness: "标签宽度" + labelScale: "标签比例" + centered: "居中" + captionMain: "标题(大)" + captionSub: "标题(小)" + availableVariables: "可修改的变量" + withQrCode: "二维码" + backgroundColor: "背景颜色" + textColor: "文本颜色" + font: "字体" + fontSerif: "衬线字体" + fontSansSerif: "无衬线字体" + quitWithoutSaveConfirm: "放弃未保存的更改?" + failedToLoadImage: "图片加载失败" +_compression: + _quality: + high: "高质量" + medium: "中质量" + low: "低质量" + _size: + large: "大" + medium: "中" + small: "小" +_order: + newest: "从新到旧" + oldest: "从旧到新" _chat: + messages: "消息" noMessagesYet: "还没有消息" newMessage: "新消息" individualChat: "私聊" - individualChat_description: "可以与特定用户进行一对一聊天。" + individualChat_description: "与特定的用户单独聊天。" roomChat: "群聊" - roomChat_description: "可以进行多人聊天。\n就算用户未允许私聊,只要接受了邀请,仍可以聊天。" - createRoom: "创建房间" - inviteUserToChat: "邀请用户来开始聊天" - yourRooms: "已创建的房间" - joiningRooms: "已加入的房间" + roomChat_description: "支持多人同时聊天。\n即使对方不允许私聊,只要接受邀请也能加入。" + createRoom: "创建群聊" + inviteUserToChat: "邀请用户来聊天吧" + yourRooms: "已创建的群聊" + joiningRooms: "已加入的群聊" invitations: "邀请" noInvitations: "没有邀请" history: "历史" noHistory: "没有历史记录" - noRooms: "没有房间" + noRooms: "没有群聊" inviteUser: "邀请用户" sentInvitations: "已发送的邀请" join: "加入" ignore: "忽略" - leave: "退出房间" + leave: "退出群聊" members: "成员" searchMessages: "搜索消息" home: "首页" send: "发送" newline: "换行" - muteThisRoom: "静音此房间" - deleteRoom: "删除房间" + muteThisRoom: "消息免打扰" + deleteRoom: "删除群聊" chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。" chatIsReadOnlyForThisAccountOrServer: "此服务器或者账户内的聊天为只读。无法发布新信息或创建及加入群聊。" - chatNotAvailableInOtherAccount: "对方账户目前处于无法使用聊天的状态。" - cannotChatWithTheUser: "无法与此用户聊天" + chatNotAvailableInOtherAccount: "对方的账户当前无法使用私信。" + cannotChatWithTheUser: "无法私信该用户" cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。" - chatWithThisUser: "聊天" + youAreNotAMemberOfThisRoomButInvited: "您尚未加入此群组,但已收到加入邀请。请接受邀请加入。" + doYouAcceptInvitation: "要接受邀请吗?" + chatWithThisUser: "私信" thisUserAllowsChatOnlyFromFollowers: "此用户仅接受关注者发起的聊天。" thisUserAllowsChatOnlyFromFollowing: "此用户仅接受关注的人发起的聊天。" thisUserAllowsChatOnlyFromMutualFollowing: "此用户仅接受互相关注的人发起的聊天。" @@ -1417,16 +1534,36 @@ _settings: makeEveryTextElementsSelectable: "使所有的文字均可选择" makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。" useStickyIcons: "使图标跟随滚动" + enableHighQualityImagePlaceholders: "显示高质量图像的占位符" + uiAnimations: "UI 动画" showNavbarSubButtons: "在导航栏中显示副按钮" ifOn: "启用时" ifOff: "关闭时" + enableSyncThemesBetweenDevices: "在设备间同步已安装的主题" + enablePullToRefresh: "开启下拉刷新" + enablePullToRefresh_description: "使用鼠标时按下滚轮来拖动" + realtimeMode_description: "与服务器建立连接并实时更新内容。将会增加流量和电池消耗。" + contentsUpdateFrequency: "内容获取频率" + contentsUpdateFrequency_description: "设置越高,内容更新越实时,但性能会降低,并且会消耗更多的流量和电池。" + contentsUpdateFrequency_description2: "当实时模式开启时,无论此设置如何,内容都会实时更新。" + showUrlPreview: "显示 URL 预览" + showAvailableReactionsFirstInNote: "在顶部显示可用的回应" + showPageTabBarBottom: "在下方显示页面标签栏" + emojiPaletteBanner: "可以将固定显示在表情符号选择器中的预设注册为调色板,也可以自定义表情符号选择器的显示方式。" + enableAnimatedImages: "启用动画图像" + settingsPersistence_title: "设置持久化" + settingsPersistence_description1: "启用设置持久化可防止设置信息丢失。" + settingsPersistence_description2: "根据环境不同,有可能无法开启。" _chat: showSenderName: "显示发送者的名字" sendOnEnter: "回车键发送" _preferencesProfile: - profileName: "配置名" + profileName: "配置文件名" profileNameDescription: "请指定用于识别此设备的名称" profileNameDescription2: "如「PC」、「手机」等" + manageProfiles: "管理配置文件" + shareSameProfileBetweenDevicesIsNotRecommended: "不建议在多个设备间共用同一个配置文件。" + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "若想在多个设备间同步某些设置,请为每个设置打开「多设备间同步」选项。" _preferencesBackup: autoBackup: "自动备份" restoreFromBackup: "从备份恢复" @@ -1436,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "需指定配置名以开启自动备份。" autoPreferencesBackupIsNotEnabledForThisDevice: "此设备未开启自动备份" backupFound: "已找到备份" + forceBackup: "强制备份设置" _accountSettings: requireSigninToViewContents: "需要登录才能显示内容" requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。" @@ -1465,6 +1603,7 @@ _delivery: manuallySuspended: "手动停止中" goneSuspended: "因服务器被删除而停止" autoSuspendedForNotResponding: "因服务器无应答而停止" + softwareSuspended: "因有停止投递的软件而停止" _bubbleGame: howToPlay: "游戏说明" hold: "抓住" @@ -1489,13 +1628,13 @@ _announcement: tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验下降。请考虑归档已完成的公告。" readConfirmTitle: "标记为已读?" readConfirmText: "阅读“{title}”的内容并将其标记为已读。" - shouldNotBeUsedToPresentPermanentInfo: "我们建议使用公告来发布临时性的流动信息而不是固定的常规信息,因为这可能损害用户体验,尤其是对于新用户而言。" + shouldNotBeUsedToPresentPermanentInfo: "因可能损坏新用户的 UX 体验,建议将通知用于发布具有时效性的信息,而不是用于长期展示的信息。" dialogAnnouncementUxWarn: "同时存在 2 个或以上的对话框公告极有可能对用户体验产生负面的影响,建议谨慎使用。" silence: "不发送通知" silenceDescription: "开启后,此条公告将不会发送通知,也不强制用户阅读。" _initialAccountSetting: accountCreated: "账户创建完成了!" - letsStartAccountSetup: "来进行帐户的初始设置吧。" + letsStartAccountSetup: "马上来进行账户的初始设定吧。" letsFillYourProfile: "首先,来设定你的个人档案吧!" profileSetting: "个人资料设置" privacySetting: "隐私设置" @@ -1507,7 +1646,7 @@ _initialAccountSetting: haveFun: "希望 {name} 在这里玩得开心!" youCanContinueTutorial: "您可以继续了解 {name}(Misskey) 的使用教程,也可以在此停止教程并立即开始使用它。\n" startTutorial: "开始教学" - skipAreYouSure: "要跳过初始设置吗?" + skipAreYouSure: "要跳过初始设定吗?" laterAreYouSure: "要稍后再进行初始设定吗?" _initialTutorial: launchTutorial: "观看教学" @@ -1591,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "回退到数据库" fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。" reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。" + remoteNotesCleaning: "自动清理远程投稿" + remoteNotesCleaning_description: "启用后,将自动清理已无法找到的旧的远程投稿,可减缓数据库的增长。" + remoteNotesCleaningMaxProcessingDuration: "最长清理持续时间" + remoteNotesCleaningExpiryDaysForEachNotes: "最短帖子保留期限" inquiryUrl: "联络地址" inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。" openRegistration: "开放注册" - openRegistrationWarning: "开放注册有风险。建议仅当能够持续监控服务器并在出现问题时能够立即响应时才打开它。" + openRegistrationWarning: "开放注册有风险。建议仅当能够持续监控服务器,并在出现问题时能够立即响应时才打开它。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "若在一段时间内没有检测到管理活动,为防止垃圾信息,此设定将自动关闭。" + deliverSuspendedSoftware: "停止投递的软件" + deliverSuspendedSoftwareDescription: "可因安全漏洞之类的原因,停止向指定的服务器及服务器版本送信。版本信息由服务器提供,不保证可靠性。可使用 semver 范围来指定版本,但指定 >= 2024.3.1 将不包括如 2024.3.1-custom.0 等自定义版本,因此建议像 >= 2024.3.1-0 这样指定 prerelease 版本。" + singleUserMode: "单用户模式" + singleUserMode_description: "若此服务器只有自己使用,开启此模式将最佳化性能。" + signToActivityPubGet: "对 GET 请求签名" + signToActivityPubGet_description: "通常情况下请保持启用。若遇到联合通信方面的问题,将其关闭可能会有所改善,但另一方面有可能会造成无法通信。" + proxyRemoteFiles: "代理远程文件" + proxyRemoteFiles_description: "如果启用,远程服务器的文件将由代理提供。可有效保护图像预览缩略图的生成与用户隐私。" + allowExternalApRedirect: "允许通过 ActivityPub 重定向查询" + allowExternalApRedirect_description: "启用时,将允许其它服务器通过此服务器查询第三方内容,但有可能导致内容欺骗。" + userGeneratedContentsVisibilityForVisitor: "用户生成内容对非用户的可见性" + userGeneratedContentsVisibilityForVisitor_description: "对于防止诸如难以管理的不适当的远程内容通过自己的服务器意外地在互联网上公开等问题很有用。" + userGeneratedContentsVisibilityForVisitor_description2: "包含服务器接收到的远程内容在内,无条件将服务器上的所有内容公开在互联网上存在风险。特别是对去中心化的特性不是很了解的访问者有可能将远程服务器上的内容误认为是在此服务器内生成的,需要特别留意。" + restartServerSetupWizardConfirm_title: "要重新开始服务器初始设定向导吗?" + restartServerSetupWizardConfirm_text: "现有的部分设定将重置。" + entrancePageStyle: "入口页面样式" + showTimelineForVisitor: "显示时间线" + showActivitiesForVisitor: "显示活动" + _userGeneratedContentsVisibilityForVisitor: + all: "全部公开" + localOnly: "仅公开本地内容,隐藏远程内容" + none: "全部隐藏" _accountMigration: moveFrom: "从别的账号迁移到此账户" moveFromSub: "为另一个账户建立别名" @@ -1697,7 +1862,7 @@ _achievements: _login500: title: "老熟人Ⅰ" description: "累计登录 500 天" - flavor: "诸君,我喜欢贴文" + flavor: "诸君,我喜欢帖文" _login600: title: "老熟人Ⅱ" description: "累计登录 600 天" @@ -1716,7 +1881,7 @@ _achievements: flavor: "感谢您使用 Misskey!" _noteClipped1: title: "忍不住要收藏到便签" - description: "第一次将贴文贴进便签" + description: "第一次将帖子加入便签" _noteFavorited1: title: "观星者" description: "第一次将帖子加入收藏" @@ -1816,7 +1981,7 @@ _achievements: description: "试图对网盘中的文件夹进行循环嵌套" _reactWithoutRead: title: "有好好读过吗?" - description: "在含有 100 字以上的帖子被发出三秒内做出回应" + description: "在含有100字以上的帖子被发出三秒内做出回应" _clickedClickHere: title: "点这里" description: "点了这里" @@ -1869,7 +2034,7 @@ _role: name: "角色名称" description: "角色描述" permission: "角色权限" - descriptionOfPermission: "监察员可以执行基本地审核操作。\n管理员可以更改服务器的所有设置。" + descriptionOfPermission: "监察员可以执行基本的审核操作。\n管理员可以更改实例的所有设置。" assignTarget: "授权对象" descriptionOfAssignTarget: "手动指手动选择谁被包括在这个角色中。\n符合条件指设置条件以自动包括符合条件的用户。" manual: "手动" @@ -1913,20 +2078,23 @@ _role: canManageCustomEmojis: "管理自定义表情符号" canManageAvatarDecorations: "管理头像挂件" driveCapacity: "网盘容量" + maxFileSize: "可上传的最大文件大小" + maxFileSize_caption: "可能在反向代理或 CDN 等前端存在其它设定值。" alwaysMarkNsfw: "总是将文件标记为 NSFW" canUpdateBioMedia: "可以更新头像和横幅" pinMax: "帖子置顶数量限制" antennaMax: "可创建的最大天线数量" - wordMuteMax: "隐藏词的字数限制" + wordMuteMax: "折叠词的字数限制" webhookMax: "Webhook 创建数量限制" clipMax: "便签创建数量限制" - noteEachClipsMax: "单个便签内的贴文数量限制" + noteEachClipsMax: "便签内贴文的最大数量" userListMax: "用户列表创建数量限制" userEachUserListsMax: "单个用户列表内用户数量限制" rateLimitFactor: "速率限制" descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "可以隐藏广告" canSearchNotes: "是否可以搜索帖子" + canSearchUsers: "使用用户检索" canUseTranslator: "使用翻译功能" avatarDecorationLimit: "可添加头像挂件的最大个数" canImportAntennas: "允许导入天线" @@ -1934,7 +2102,13 @@ _role: canImportFollowing: "允许导入关注列表" canImportMuting: "允许导入隐藏列表" canImportUserLists: "允许导入用户列表" - chatAvailability: "允许聊天" + chatAvailability: "允许私信" + uploadableFileTypes: "可上传的文件类型" + uploadableFileTypes_caption: "指定 MIME 类型。可用换行指定多个类型,也可以用星号(*)作为通配符。(如 image/*)" + uploadableFileTypes_caption2: "文件根据文件的不同,可能无法判断其类型。若要允许此类文件,请在指定中添加 {x}。" + noteDraftLimit: "可在服务器上创建多少草稿" + scheduledNoteLimit: "可同时创建的定时帖子数量" + watermarkAvailable: "能否使用水印功能" _condition: roleAssignedTo: "已分配给手动角色" isLocal: "是本地用户" @@ -1999,9 +2173,9 @@ _forgotPassword: ifNoEmail: "如果您没有设置电子邮件地址,请联系管理员。" contactAdmin: "该服务器不支持发送电子邮件。如果您想重设密码,请联系管理员。" _gallery: - my: "我的图库" - liked: "喜欢的图片" - like: "喜欢" + my: "我的图集" + liked: "喜欢的图集" + like: "喜欢!" unlike: "取消喜欢" _email: _follow: @@ -2061,32 +2235,32 @@ _instanceTicker: _serverDisconnectedBehavior: reload: "自动重载" dialog: "对话框警告" - quiet: "安静警告" + quiet: "静默警告" _channel: create: "创建频道" edit: "编辑频道" setBanner: "设置横幅" removeBanner: "删除横幅" - featured: "热点" - owned: "管理中" + featured: "热门" + owned: "正在管理" following: "正在关注" - usersCount: "有 {n} 人参与" - notesCount: "有 {n} 个帖子" + usersCount: "有{n}人参与" + notesCount: "有{n}个帖子" nameAndDescription: "名称与描述" nameOnly: "仅名称" - allowRenoteToExternal: "允许在频道外转帖及引用" + allowRenoteToExternal: "允许转发到频道外和引用" _menuDisplay: sideFull: "横向" sideIcon: "横向(图标)" top: "顶部" hide: "隐藏" _wordMute: - muteWords: "要隐藏的词" + muteWords: "要屏蔽的词" muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。" muteWordsDescription2: "正则表达式用斜线包裹" _instanceMute: - instanceMuteDescription: "隐藏服务器中的所有帖子和转帖,包括这些服务器上的用户回复。" - instanceMuteDescription2: "一行一个" + instanceMuteDescription: "隐藏来自这些服务器的所有帖子和转贴,包括这些服务器上用户的回复。" + instanceMuteDescription2: "通过换行符分隔进行设置" title: "下面实例中的帖子将被隐藏。" heading: "已隐藏的服务器" _theme: @@ -2094,6 +2268,7 @@ _theme: install: "安装主题" manage: "主题管理" code: "主题代码" + copyThemeCode: "复制主题代码" description: "描述" installed: "{name} 已安装" installedThemes: "已安装的主题" @@ -2152,7 +2327,6 @@ _theme: buttonBg: "按钮背景" buttonHoverBg: "按钮背景(悬停)" inputBorder: "输入框边框" - driveFolderBg: "网盘的文件夹背景" badge: "徽章" messageBg: "聊天背景" fgHighlighted: "高亮显示文本" @@ -2161,7 +2335,7 @@ _sfx: noteMy: "我的帖子" notification: "通知" reaction: "选择回应时" - chatMessage: "聊天信息" + chatMessage: "私信" _soundSettings: driveFile: "使用网盘内的音频" driveFileWarn: "选择网盘上的文件" @@ -2172,28 +2346,29 @@ _soundSettings: driveFileError: "无法读取声音。请更改设置。" _ago: future: "未来" - justNow: "最近" - secondsAgo: "{n} 秒前" - minutesAgo: "{n} 分前" - hoursAgo: "{n} 小时前" - daysAgo: "{n} 日前" - weeksAgo: "{n} 周前" - monthsAgo: "{n} 月前" - yearsAgo: "{n} 年前" + justNow: "刚刚" + secondsAgo: "{n}秒前" + minutesAgo: "{n}分钟前" + hoursAgo: "{n}小时前" + daysAgo: "{n}天前" + weeksAgo: "{n}周前" + monthsAgo: "{n}个月前" + yearsAgo: "{n}年前" invalid: "没有" _timeIn: seconds: "{n}秒后" - minutes: "{n} 分后" - hours: "{n} 小时后" + minutes: "{n}分钟后" + hours: "{n}小时后" days: "{n}天后" - weeks: "{n} 周后" - months: "{n} 月后" - years: "{n} 年后" + weeks: "{n}周后" + months: "{n}个月后" + years: "{n}年后" _time: second: "秒" - minute: "分" + minute: "分钟" hour: "小时" - day: "日" + day: "天" + month: "个月" _2fa: alreadyRegistered: "此设备已被注册" registerTOTP: "开始设置验证器" @@ -2234,28 +2409,28 @@ _permissions: "write:favorites": "编辑收藏夹" "read:following": "查看关注信息" "write:following": "关注/取消关注" - "read:messaging": "查看消息" + "read:messaging": "查看私信" "write:messaging": "撰写或删除消息" - "read:mutes": "查看隐藏列表" - "write:mutes": "编辑隐藏列表" + "read:mutes": "查看已隐藏用户列表" + "write:mutes": "编辑已隐藏用户列表" "write:notes": "撰写或删除帖子" "read:notifications": "查看通知" "write:notifications": "管理通知" "read:reactions": "查看回应" - "write:reactions": "回应操作" + "write:reactions": "编辑回应" "write:votes": "投票" "read:pages": "查看页面" - "write:pages": "操作页面" + "write:pages": "编辑页面" "read:page-likes": "查看喜欢的页面" - "write:page-likes": "操作喜欢的页面" + "write:page-likes": "管理喜欢的页面" "read:user-groups": "查看用户组" - "write:user-groups": "操作用户组" + "write:user-groups": "编辑用户组" "read:channels": "查看频道" "write:channels": "管理频道" - "read:gallery": "浏览图库" - "write:gallery": "操作图库" - "read:gallery-likes": "读取喜欢的图片" - "write:gallery-likes": "操作喜欢的图片" + "read:gallery": "浏览图集" + "write:gallery": "编辑图集" + "read:gallery-likes": "浏览喜欢的图集" + "write:gallery-likes": "管理喜欢的图集" "read:flash": "查看 Play" "write:flash": "编辑 Play" "read:flash-likes": "查看 Play 的点赞" @@ -2283,33 +2458,33 @@ _permissions: "read:admin:roles": "查看角色" "write:admin:relays": "编辑中继" "read:admin:relays": "查看中继" - "write:admin:invite-codes": "编辑邀请码" + "write:admin:invite-codes": "管理邀请码" "read:admin:invite-codes": "查看邀请码" - "write:admin:announcements": "编辑公告" + "write:admin:announcements": "管理公告" "read:admin:announcements": "查看公告" "write:admin:avatar-decorations": "编辑头像挂件" "read:admin:avatar-decorations": "查看头像挂件" "write:admin:federation": "编辑联合相关信息" "write:admin:account": "编辑用户账户" "read:admin:account": "查看用户相关情报" - "write:admin:emoji": "编辑表情文字" - "read:admin:emoji": "查看表情文字" + "write:admin:emoji": "编辑表情符号" + "read:admin:emoji": "查看表情符号" "write:admin:queue": "编辑作业队列" "read:admin:queue": "查看作业队列相关情报" "write:admin:promo": "运营推广说明" - "write:admin:drive": "编辑用户网盘" + "write:admin:drive": "管理用户网盘" "read:admin:drive": "查看用户网盘相关情报" "read:admin:stream": "使用管理员用的 Websocket API" - "write:admin:ad": "编辑广告" + "write:admin:ad": "管理广告" "read:admin:ad": "查看广告" "write:invite-codes": "生成邀请码" "read:invite-codes": "获取已发行的邀请码" - "write:clip-favorite": "编辑便签的点赞" + "write:clip-favorite": "管理喜欢的便签" "read:clip-favorite": "查看便签的点赞" "read:federation": "查看联合相关信息" "write:report-abuse": "举报用户" "write:chat": "撰写或删除消息" - "read:chat": "查看聊天" + "read:chat": "查看私信" _auth: shareAccessTitle: "应用程序授权许可" shareAccess: "您要授权允许 “{name}” 访问您的帐户吗?" @@ -2323,12 +2498,13 @@ _auth: scopeUser: "以下面的用户进行操作" pleaseLogin: "在对应用进行授权许可之前,请先登录" byClickingYouWillBeRedirectedToThisUrl: "允许访问后将会自动重定向到以下 URL" + alreadyAuthorized: "此应用已有访问许可。" _antennaSources: all: "所有帖子" homeTimeline: "已关注用户的帖子" users: "来自指定用户的帖子" userList: "来自指定列表中的帖子" - userBlacklist: "除掉已选择用户后所有的帖子" + userBlacklist: "过滤指定用户后的所有帖子" _weekday: sunday: "星期日" monday: "星期一" @@ -2340,7 +2516,7 @@ _weekday: _widgets: profile: "个人资料" instanceInfo: "服务器信息" - memo: "便签" + memo: "便利贴" notifications: "通知" timeline: "时间线" calendar: "日历" @@ -2353,11 +2529,11 @@ _widgets: digitalClock: "数字时钟" unixClock: "UNIX 时钟" federation: "联合" - instanceCloud: "服务器云" - postForm: "投稿窗口" + instanceCloud: "服务器球状列表" + postForm: "发帖窗口" slideshow: "幻灯片展示" button: "按钮" - onlineUsers: "在线用户" + onlineUsers: "在线用户数" jobQueue: "作业队列" serverMetric: "服务器指标" aiscript: "AiScript 控制台" @@ -2368,7 +2544,45 @@ _widgets: chooseList: "选择列表" clicker: "点击器" birthdayFollowings: "今天是他们的生日" - chat: "聊天" + chat: "私信" +_widgetOptions: + showHeader: "显示标题" + transparent: "使背景透明" + height: "高度" + _button: + colored: "彩色" + _clock: + size: "大小" + thickness: "指针宽度" + thicknessThin: "细" + thicknessMedium: "普通" + thicknessThick: "粗" + graduations: "表盘刻度" + graduationDots: "点" + graduationArabic: "阿拉伯数字" + fadeGraduations: "淡化表盘" + sAnimation: "秒针动画" + sAnimationElastic: "跳动" + sAnimationEaseOut: "平滑" + twentyFour: "24 小时制" + labelTime: "时间" + labelTz: "时区" + labelTimeAndTz: "时间和时区" + timezone: "时区" + showMs: "显示毫秒" + showLabel: "显示标签" + _jobQueue: + sound: "播放音效" + _rss: + url: "RSS feed 的 URL" + refreshIntervalSec: "更新间隔(秒)" + maxEntries: "最大显示个数" + _rssTicker: + shuffle: "随机顺序" + duration: "滚动速度(秒)" + reverse: "反方向滚动" + _birthdayFollowings: + period: "期限" _cw: hide: "隐藏" show: "查看更多" @@ -2376,26 +2590,26 @@ _cw: files: "{count} 个文件" _poll: noOnlyOneChoice: "需要至少两个选项" - choiceN: "选择 {n}" + choiceN: "选项{n}" noMore: "无法再添加更多了" - canMultipleVote: "允许多个投票" + canMultipleVote: "允许多选" expiration: "截止时间" infinite: "永久" at: "指定日期" after: "指定时间" deadlineDate: "截止日期" - deadlineTime: "小时" - duration: "时长" - votesCount: "{n} 票" - totalVotes: "总票数 {n}" + deadlineTime: "时间" + duration: "期限" + votesCount: "{n}票" + totalVotes: "总计{n}票" vote: "投票" - showResult: "显示结果" + showResult: "查看结果" voted: "已投票" closed: "已截止" - remainingDays: "{d} 天 {h} 小时后截止" - remainingHours: "{h} 小时 {m} 分后截止" - remainingMinutes: "{m} 分 {s} 秒后截止" - remainingSeconds: "{s} 秒后截止" + remainingDays: "{d}天{h}小时后截止" + remainingHours: "{h}小时{m}分后截止" + remainingMinutes: "{m}分{s}秒后截止" + remainingSeconds: "{s}秒后截止" _visibility: public: "公开" publicDescription: "您的帖子将出现在全局时间线上" @@ -2408,13 +2622,29 @@ _visibility: disableFederation: "不参与联合" disableFederationDescription: "不发送到其他服务器" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "还有未上传的文件,要丢弃并关闭窗口吗?" + uploaderTip: "文件还未上传。可以在文件菜单中进行重命名、裁剪、添加水印、设置是否压缩等操作。文件将在发帖时自动上传。" 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: "发生了什么?" - c: "你有什么想法?" + a: "现在怎么样?" + b: "想好发些什么了吗?" + c: "在想些什么呢?" d: "你想要发布些什么吗?" e: "请写下来吧" f: "等待您的发布..." @@ -2434,7 +2664,7 @@ _profile: avatarDecorationMax: "最多可添加 {max} 个挂件" followedMessage: "被关注时显示的消息" followedMessageDescription: "可以设置被关注时向对方显示的短消息。" - followedMessageDescriptionForLockedAccount: "需要批准才能关注的情况下,消息是在请求被批准后显示。" + followedMessageDescriptionForLockedAccount: "需要批准才能关注的情况下,消息会在请求被批准后显示。" _exportOrImport: allNotes: "所有帖子" favoritedNotes: "收藏的帖子" @@ -2443,7 +2673,7 @@ _exportOrImport: muteList: "隐藏" blockingList: "屏蔽" userLists: "列表" - excludeMutingUsers: "排除屏蔽用户" + excludeMutingUsers: "排除已隐藏用户" excludeInactiveUsers: "排除不活跃用户" withReplies: "在时间线中包含导入用户的回复" _charts: @@ -2487,7 +2717,7 @@ _play: editThisPage: "编辑此 Play" viewSource: "查看源代码" my: "我的 Play" - liked: "点赞的 Play" + liked: "喜欢的 Play" featured: "热门" title: "标题" script: "脚本" @@ -2504,7 +2734,7 @@ _pages: editThisPage: "编辑此页面" viewSource: "查看源代码" viewPage: "查看页面" - like: "赞" + like: "喜欢" unlike: "取消喜欢" my: "我的页面" liked: "喜欢的页面" @@ -2552,14 +2782,16 @@ _notification: youGotReply: "来自{name}的回复" youGotQuote: "来自{name}的引用" youRenoted: "来自{name}的转发" - youWereFollowed: "关注了你。" + youWereFollowed: "关注了你" youReceivedFollowRequest: "您有新的关注请求" yourFollowRequestAccepted: "您的关注请求已通过" pollEnded: "问卷调查结果已生成。" + scheduledNotePosted: "定时帖子已发布" + scheduledNotePostFailed: "定时帖子发布失败" newNote: "新的帖子" unreadAntennaNote: "天线 {name}" roleAssigned: "授予的角色" - chatRoomInvitationReceived: "受邀加入聊天室" + chatRoomInvitationReceived: "您已被邀请加入群聊" emptyPushNotificationMessage: "推送通知已更新" achievementEarned: "获得成就" testNotification: "测试通知" @@ -2585,10 +2817,12 @@ _notification: quote: "引用" reaction: "回应" pollEnded: "问卷调查结束" + scheduledNotePosted: "定时发送成功" + scheduledNotePostFailed: "定时发送失败" receiveFollowRequest: "收到关注请求" followRequestAccepted: "关注请求已通过" roleAssigned: "授予的角色" - chatRoomInvitationReceived: "受邀加入聊天室" + chatRoomInvitationReceived: "您已被邀请加入群聊" achievementEarned: "取得的成就" exportCompleted: "已完成导出" login: "登录" @@ -2620,10 +2854,18 @@ _deck: introduction: "将各列进行组合以创建您自己的界面!" introduction2: "可以随时通过屏幕右侧的 + 来添加列" widgetsIntroduction: "从列菜单中,选择“小工具编辑”来添加小工具" - useSimpleUiForNonRootPages: "用简易UI表示非根页面" + useSimpleUiForNonRootPages: "使用简易UI显示导航页面" usedAsMinWidthWhenFlexible: "「自适应宽度」被启用的时候,这就是最小的宽度" flexible: "自适应宽度" - enableSyncBetweenDevicesForProfiles: "启用个人资料信息跨设备同步" + enableSyncBetweenDevicesForProfiles: "启用配置文件跨设备同步" + showHowToUse: "查看用户界面说明" + _howToUse: + addColumn_title: "添加列" + addColumn_description: "可以选择要添加的列的类型。" + settings_title: "用户界面设置" + settings_description: "可以配置 Deck UI 的详细设置," + switchProfile_title: "切换配置文件" + switchProfile_description: "将用户界面布局保存为配置文件,以便随时切换。" _columns: main: "主列" widgets: "小工具" @@ -2635,7 +2877,7 @@ _deck: mentions: "提及" direct: "指定用户" roleTimeline: "角色时间线" - chat: "聊天" + chat: "私信" _dialog: charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}" charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}" @@ -2655,7 +2897,7 @@ _webhookSettings: _events: follow: "关注时" followed: "被关注时" - note: "发布贴文时" + note: "发布帖文时" reply: "收到回复时" renote: "被转发时" reaction: "被回应时" @@ -2684,6 +2926,8 @@ _abuseReport: notifiedWebhook: "使用的 webhook" deleteConfirm: "要删除通知吗?" _moderationLogTypes: + clearQueue: "清除队列" + promoteQueue: "重新执行队列中的任务" createRole: "创建角色" deleteRole: "删除角色" updateRole: "更新角色" @@ -2728,11 +2972,11 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "新建了举报通知" updateAbuseReportNotificationRecipient: "更新了举报通知" deleteAbuseReportNotificationRecipient: "删除了举报通知" - deleteAccount: "删除了账户" - deletePage: "删除了页面" - deleteFlash: "删除了 Play" - deleteGalleryPost: "删除了图库稿件" - deleteChatRoom: "删除聊天室" + deleteAccount: "删除帐户" + deletePage: "删除页面" + deleteFlash: "删除 Play" + deleteGalleryPost: "删除图集内容" + deleteChatRoom: "删除群聊" updateProxyAccountDescription: "更新代理账户的简介" _fileViewer: title: "文件信息" @@ -2741,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "添加日期" attachedNotes: "附加到的帖子" + usage: "使用" thisPageCanBeSeenFromTheAuthor: "此页只能被该文件的上传者查看。" _externalResourceInstaller: title: "从外部站点安装" @@ -2788,9 +3033,12 @@ _dataSaver: _avatar: title: "头像" description: "停止播放头像的动画。 由于动画图片的文件大小可能比普通图像大,这可以进一步减少数据流量。" - _urlPreview: - title: "URL预览缩略图\n" + _urlPreviewThumbnail: + title: "不显示 URL预览缩略图" description: "将不再加载 URL 预览缩略图。" + _disableUrlPreview: + title: "禁用 URL 预览" + description: "关闭 URL 预览功能。与预览缩略图不同,减少了链接信息的加载。" _code: title: "代码高亮" description: "如果使用了代码高亮标记,例如在 MFM 中,则在点击之前不会加载。 代码高亮要求加载每种高亮语言的定义文件,由于这些文件不再自动加载,因此有望减少数据传输量。" @@ -2848,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "设置 URL 预览" enable: "启用 URL 预览" + allowRedirect: "允许预览目标的重定向" + allowRedirectDescription: "如果输入的 URL 被重定向,可设置是否跟随重定向目标并显示预览。禁用此选项将节省服务器资源,但重定向目标的内容将不会显示。" timeout: "超时阈值(ms)" timeoutDescription: "如果获取预览所用时间超过这个值,则不生成预览。" maximumContentLength: "Content-Length 的最大值(byte)" @@ -2864,10 +3114,10 @@ _mediaControls: playbackRate: "播放速度" loop: "循环播放" _contextMenu: - title: "上下文菜单" - app: "应用" - appWithShift: "Shift 键应用" - native: "浏览器的用户界面" + title: "右键菜单" + app: "使用" + appWithShift: "按住 Shift 键使用" + native: "浏览器的原生界面" _gridComponent: _error: requiredValue: "此值为必填项" @@ -2921,10 +3171,6 @@ _customEmojisManager: uploadSettingDescription: "可以在此页面设置上传表情符号时的行为。" directoryToCategoryLabel: "将目录名设为「category」" directoryToCategoryCaption: "拖放目录时,将目录名设置为「category」" - emojiInputAreaCaption: "请使用其中一种方法选择要注册的表情符号。" - emojiInputAreaList1: "在此区域内拖放图像文件或者目录" - emojiInputAreaList2: "单击此链接以从电脑中选择" - emojiInputAreaList3: "单击此链接以从网盘中选择" confirmRegisterEmojisDescription: "要将列表内显示的表情符号替换为新的自定义表情符号吗?(为降低服务器负载,一次操作最多只能注册 {count} 个表情符号)" confirmClearEmojisDescription: "要放弃编辑并将列表内表示的表情符号清空吗?" confirmUploadEmojisDescription: "要将拖放的 {count} 个文件上传到网盘上吗?" @@ -2949,8 +3195,8 @@ _selfXssPrevention: description2: "如果不能完全理解将要粘贴的内容,%c 请立即停止操作并关闭这个窗口。" description3: "详情请看这里。{link}" _followRequest: - recieved: "已收到申请" - sent: "已发送申请" + recieved: "收到的请求" + sent: "发送的请求" _remoteLookupErrors: _federationNotAllowed: title: "无法与此服务器通信" @@ -2985,13 +3231,14 @@ _bootErrors: serverError: "请稍等片刻再重试。若问题仍无法解决,请将以下 Error ID 一起发送给管理员。" solution: "以下方法或许可以解决问题:" solution1: "将浏览器及操作系统更新到最新版本" - solution2: "禁用广告屏蔽插件" + solution2: "禁用广告拦截插件" solution3: "清除浏览器缓存" solution4: "(Tor Browser)将 dom.webaudio.enabled 设定为 true" otherOption: "其它选项" otherOption1: "清除客户端设定与缓存" otherOption2: "使用简易客户端" otherOption3: "启动修复工具" + otherOption4: "以安全模式启动 Misskey" _search: searchScopeAll: "全部" searchScopeLocal: "本地" @@ -3000,3 +3247,196 @@ _search: pleaseEnterServerHost: "请填写服务器主机名" pleaseSelectUser: "请选择用户" serverHostPlaceholder: "如:misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey 安装完成!" + firstCreateAccount: "首先,创建一个管理员帐户。" + accountCreated: "管理员账号已创建!" + serverSetting: "服务器设置" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "用此向导来轻松地以最佳方式配置服务器。" + settingsYouMakeHereCanBeChangedLater: "这里的设置在之后也能更改。" + howWillYouUseMisskey: "打算怎样使用 Misskey?" + _use: + single: "单用户服务器" + single_description: "仅供自己使用的单人服务器" + single_youCanCreateMultipleAccounts: "使用单用户服务器模式使用时,也可以根据需要创建多个账号。" + group: "群组服务器" + group_description: "邀请其他可信用户一起使用的多人服务器" + open: "开放服务器" + open_description: "以容纳不限定数量的用户的模式运行" + openServerAdvice: "容纳不限定数量的用户有风险。推荐建立能应对各种问题的强大的管理体制来运营。" + openServerAntiSpamAdvice: "为防止自己的服务器成为广告发信基地,请打开如 reCAPTCHA 等 Bot 防御功能,并谨慎关注安全性。" + howManyUsersDoYouExpect: "预计会有多少用户?" + _scale: + small: "100 人以下(小规模)" + medium: "100 人以上 1000 人以下(中规模)" + large: "1000 人以上(大规模)" + largeScaleServerAdvice: "运营大规模服务器可能需要高级基础设施知识,如负载均衡和数据库复制。" + doYouConnectToFediverse: "要加入 Fediverse 吗?" + doYouConnectToFediverse_description1: "若加入由分散性服务器所构成的网络(Fediverse),将能与其它服务器交换内容。" + doYouConnectToFediverse_description2: "加入 Fediverse 在这里被称为「联合」。" + youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。" + remoteContentsCleaning: "自动清理传入内容" + remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。" + adminInfo: "管理员信息" + adminInfo_description: "设置用于接受询问的管理员信息。" + adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。" + followingSettingsAreRecommended: "推荐以下设置" + applyTheseSettings: "使用此设置" + skipSettings: "跳过设置" + settingsCompleted: "设置完成!" + settingsCompleted_description: "辛苦了。设置已完成,可以立即开始使用服务器了。" + settingsCompleted_description2: "服务器的详细设置可在「控制面板」进行。" + donationRequest: "请求捐助" + _donationRequest: + text1: "Misskey 是由志愿者开发的免费软件。" + text2: "为了今后也能继续开发,如果可以的话,请考虑一下捐助。" + text3: "也有面向支援者的特典!" +_uploader: + editImage: "编辑图像" + compressedToX: "压缩 {x}" + savedXPercent: "节省了 {x}% 的空间" + abortConfirm: "还有未上传的文件,要中止吗?" + doneConfirm: "部分文件尚未上传,是否继续?" + maxFileSizeIsX: "可上传最大 {x} 的文件。" + allowedTypes: "可上传的文件类型" + tip: "文件还没有被上传。可在此对话框中进行上传前确认、重命名、压缩、裁剪等操作。准备完成后,点击「上传」即可开始上传。" +_clientPerformanceIssueTip: + title: "如果觉得电池耗电过高" + makeSureDisabledAdBlocker: "请关闭广告拦截器" + makeSureDisabledAdBlocker_description: "广告拦截器会影响性能。请检查操作系统功能、浏览器功能或附加组件是否启用了广告拦截器。" + makeSureDisabledCustomCss: "请关闭自定义 CSS" + makeSureDisabledCustomCss_description: "覆盖样式可能会影响性能。请确保没有启用任何自定义 CSS 或覆盖样式的扩展。" + makeSureDisabledAddons: "请关闭扩展" + makeSureDisabledAddons_description: "某些扩展可能会干扰客户端的运行并影响性能。尝试禁用浏览器扩展并查看是否有改善。" +_clip: + tip: "便签功能可以将帖子合并在一起。" +_userLists: + tip: "可创建包含任意用户的列表。已创建的列表可作为时间线查看。" +watermark: "水印" +defaultPreset: "默认预设" +_watermarkEditor: + tip: "可在图像内增加包含作者等信息的水印。" + quitWithoutSaveConfirm: "放弃未保存的更改?" + driveFileTypeWarn: "不支持此文件" + driveFileTypeWarnDescription: "请选择图像文件" + title: "编辑水印" + cover: "覆盖所有" + repeat: "平铺" + preserveBoundingRect: "调整为旋转时不超出范围" + opacity: "不透明度" + scale: "大小" + text: "文本" + qr: "二维码" + position: "位置" + margin: "边距" + type: "类型" + image: "图片" + advanced: "高级" + angle: "角度" + stripe: "条纹" + stripeWidth: "线条宽度" + stripeFrequency: "线条数量" + polkadot: "波点" + checker: "检查" + polkadotMainDotOpacity: "主波点的不透明度" + polkadotMainDotRadius: "主波点的大小" + polkadotSubDotOpacity: "副波点的不透明度" + polkadotSubDotRadius: "副波点的大小" + polkadotSubDotDivisions: "副波点的数量" + leaveBlankToAccountUrl: "留空则为账户 URL" + failedToLoadImage: "图片加载失败" +_imageEffector: + title: "效果" + addEffect: "添加效果" + discardChangesConfirm: "丢弃当前设置并退出?" + failedToLoadImage: "图片加载失败" + _fxs: + chromaticAberration: "色差" + glitch: "故障" + mirror: "镜像" + invert: "反转颜色" + grayscale: "黑白" + blur: "模糊" + pixelate: "马赛克" + colorAdjust: "色彩校正" + colorClamp: "颜色限制" + colorClampAdvanced: "颜色限制(高级)" + distort: "失真" + threshold: "二值化" + zoomLines: "集中线" + stripe: "条纹" + polkadot: "波点" + checker: "检查" + blockNoise: "块状噪点" + tearing: "撕裂" + fill: "填充" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + radius: "半径" + samples: "采样数" + offset: "位置" + color: "颜色" + opacity: "不透明度" + normalize: "标准化" + amount: "数量" + lightness: "浅色" + contrast: "对比度" + hue: "色调" + brightness: "亮度" + saturation: "饱和度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "频率" + strength: "强度" + glitchChannelShift: "错位" + seed: "种子" + redComponent: "红色成分" + greenComponent: "绿色成分" + blueComponent: "蓝色成分" + threshold: "阈值" + centerX: "中心 X " + centerY: "中心 Y" + zoomLinesSmoothing: "平滑" + zoomLinesSmoothingDescription: "平滑和集中线宽度设置不能同时使用。" + zoomLinesThreshold: "集中线宽度" + zoomLinesMaskSize: "中心直径" + zoomLinesBlack: "变成黑色" + circle: "圆形" +drafts: "草稿" +_drafts: + select: "选择草稿" + cannotCreateDraftAnymore: "已超过可创建的草稿数量。" + cannotCreateDraft: "此内容无法创建草稿。" + delete: "删除草稿" + deleteAreYouSure: "要删除草稿吗?" + noDrafts: "没有草稿" + replyTo: "回复给 {user}" + quoteOf: "对 {user} 帖子的引用" + postTo: "向 {channel} 的投稿" + saveToDraft: "保存到草稿" + restoreFromDraft: "从草稿恢复" + restore: "恢复" + listDrafts: "草稿一览" + schedule: "定时发布" + listScheduledNotes: "定时发布列表" + cancelSchedule: "取消定时" +qr: "二维码" +_qr: + showTabTitle: "显示" + readTabTitle: "读取" + shareTitle: "{name} {acct}" + shareText: "请在 Fediverse 上关注我!" + chooseCamera: "选择相机" + cannotToggleFlash: "无法开关闪光灯" + turnOnFlash: "打开闪光灯" + turnOffFlash: "关闭闪光灯" + startQr: "重新打开二维码扫描器" + stopQr: "关闭二维码扫描器" + noQrCodeFound: "未找到二维码" + scanFile: "扫描设备上的图像" + raw: "文本" + mfm: "MFM" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index cfe3b729f0..19e4678931 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -75,7 +75,7 @@ receiveFollowRequest: "您有新的追隨請求" followRequestAccepted: "追隨請求已被接受" mention: "提及" mentions: "提及" -directNotes: "私訊" +directNotes: "指定使用者" importAndExport: "匯入與匯出" import: "匯入" export: "匯出" @@ -83,6 +83,8 @@ files: "檔案" download: "下載" driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。" unfollowConfirm: "確定要取消追隨{name}嗎?" +cancelFollowRequestConfirm: "要取消向 {name} 送出的追隨申請嗎?" +rejectFollowRequestConfirm: "要拒絕來自 {name} 的追隨申請嗎?" exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。" importRequested: "已請求匯入。這可能會花一點時間。" lists: "清單" @@ -172,7 +174,7 @@ emojiUrl: "表情符號 URL" addEmoji: "新增表情符號" settingGuide: "推薦設定" cacheRemoteFiles: "快取遠端檔案" -cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私。" +cacheRemoteFilesDescription: "啟用這個設定後,遠端檔案會被快取到這台伺服器的儲存空間中。這樣能加快圖片的顯示速度,但會多占用伺服器的儲存容量。遠端使用者能保留多少快取,取決於其角色所設定的硬碟容量上限。若超過這個上限,系統會從最舊的檔案開始刪除快取並改成連結。若停用這個設定,遠端檔案一開始就只會以連結的形式保留。" youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。" cacheRemoteSensitiveFiles: "快取遠端的敏感檔案" cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。" @@ -220,6 +222,7 @@ silenceThisInstance: "禁言此伺服器" mediaSilenceThisInstance: "將這個伺服器的媒體設為禁言" operations: "操作" software: "軟體" +softwareName: "軟體名稱" version: "版本" metadata: "詮釋資料" withNFiles: "{n} 個檔案" @@ -250,9 +253,9 @@ noUsers: "沒有任何使用者" editProfile: "編輯個人檔案" noteDeleteConfirm: "確定刪除此貼文嗎?" pinLimitExceeded: "不能置頂更多貼文了" -intro: "Misskey 部署完成!請建立管理員帳戶。" done: "完成" processing: "處理中" +preprocessing: "準備中" preview: "預覽" default: "預設" defaultValueIs: "預設值:{value}" @@ -298,8 +301,10 @@ uploadFromUrl: "從網址上傳" uploadFromUrlDescription: "您要上傳的檔案網址" uploadFromUrlRequested: "已請求上傳" uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。" +uploadNFiles: "上傳了 {n} 個檔案" explore: "探索" messageRead: "已讀" +readAllChatMessages: "將所有訊息標記為已讀" noMoreHistory: "沒有更多歷史紀錄" startChat: "開始聊天" nUsersRead: "{n} 人已讀" @@ -326,11 +331,13 @@ dark: "深色" lightThemes: "淺色佈景主題" darkThemes: "深色佈景主題" syncDeviceDarkMode: "與裝置的深色模式同步" +switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」已開啟。要關閉同步並手動切換模式嗎?\n" drive: "雲端硬碟" fileName: "檔案名稱" selectFile: "選擇檔案" selectFiles: "選擇檔案" selectFolder: "選擇資料夾" +unselectFolder: "取消選擇資料夾" selectFolders: "選擇資料夾" fileNotSelected: "尚未選擇檔案" renameFile: "重新命名檔案" @@ -343,6 +350,7 @@ addFile: "加入附件" showFile: "瀏覽文件" emptyDrive: "雲端硬碟為空" emptyFolder: "資料夾為空" +dropHereToUpload: "將檔案拖放至此處即可上傳" unableToDelete: "無法刪除" inputNewFileName: "輸入檔案名稱" inputNewDescription: "請輸入新標題 " @@ -575,8 +583,10 @@ showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框" showFixedPostFormInChannel: "於時間軸頁頂顯示「發送貼文」方框(頻道)" withRepliesByDefaultForNewlyFollowed: "在追隨其他人後,預設在時間軸納入回覆的貼文" newNoteRecived: "發現新貼文" +newNote: "新的貼文" sounds: "音效" sound: "音效" +notificationSoundSettings: "設定通知音效" listen: "聆聽" none: "無" showInPage: "在頁面中顯示" @@ -634,7 +644,7 @@ inboxUrl: "收件夾 URL" addedRelays: "已加入的中繼器" serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。" deletedNote: "已刪除的貼文" -invisibleNote: "私人貼文" +invisibleNote: "私密的貼文" enableInfiniteScroll: "啟用自動滾動頁面模式" visibility: "可見性" poll: "票選活動" @@ -768,6 +778,7 @@ lockedAccountInfo: "即使追隨需要核准,除非你將貼文的可見性設 alwaysMarkSensitive: "預設標記檔案為敏感內容" loadRawImages: "以原始圖檔顯示附件圖檔的縮圖" disableShowingAnimatedImages: "不播放動態圖檔" +disableShowingAnimatedImages_caption: "無論這個設定如何,如果動畫圖片無法播放,可能是因為瀏覽器或作業系統的無障礙設定、省電設定等產生了干擾。" highlightSensitiveMedia: "強調敏感標記" verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的連結以完成驗證。" notSet: "未設定" @@ -784,7 +795,6 @@ thisIsExperimentalFeature: "這是一項實驗性功能,其行為會隨需要 developer: "開發者" makeExplorable: "使自己的帳戶更容易被找到" makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。" -showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文" duplicate: "複製" left: "左" center: "置中" @@ -792,6 +802,7 @@ wide: "寬" narrow: "窄" reloadToApplySetting: "設定將會在頁面重新載入之後生效。要現在就重載頁面嗎?" needReloadToApply: "必須重新載入才會生效。" +needToRestartServerToApply: "必須重新啟動伺服器才會使變更生效。" showTitlebar: "顯示標題列" clearCache: "清除快取資料" onlineUsersCount: "{n} 人上線" @@ -998,6 +1009,7 @@ failedToUpload: "上傳失敗" cannotUploadBecauseInappropriate: "由於判定可能包含不適當的內容,因此無法上傳。" cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此無法上傳。" cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。" +cannotUploadBecauseUnallowedFileType: "由於檔案類型不被允許,無法上傳。\n" beta: "測試版" enableAutoSensitive: "自動 NSFW 判定" enableAutoSensitiveDescription: "如果可行,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依伺服器規則而自動啟用。" @@ -1013,6 +1025,9 @@ pushNotificationAlreadySubscribed: "推播通知啟用中" pushNotificationNotSupported: "瀏覽器或伺服器不支援推播通知" sendPushNotificationReadMessage: "如果已閱讀通知與訊息,就刪除推播通知" sendPushNotificationReadMessageCaption: "可能會導致裝置的電池消耗量增加。" +pleaseAllowPushNotification: "請允許瀏覽器的通知設定" +browserPushNotificationDisabled: "取得通知發送權限失敗" +browserPushNotificationDisabledDescription: "您沒有權限從 {serverName} 發送通知。請在瀏覽器設定中允許通知,然後再試一次。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "復原" @@ -1049,6 +1064,7 @@ permissionDeniedError: "操作被拒絕" permissionDeniedErrorDescription: "此帳戶沒有執行這個操作的權限。" preset: "預設值" selectFromPresets: "從預設值中選擇" +custom: "自訂" achievements: "成就" gotInvalidResponseError: "伺服器的回應無效" gotInvalidResponseErrorDescription: "伺服器可能已關閉或者在維護中,請稍後再試。" @@ -1073,9 +1089,9 @@ postToTheChannel: "發佈到頻道" cannotBeChangedLater: "之後不能變更。" reactionAcceptance: "接受表情反應" likeOnly: "僅限讚" -likeOnlyForRemote: "遠端僅限讚" +likeOnlyForRemote: "全部(遠端僅限讚)" nonSensitiveOnly: "僅限非敏感" -nonSensitiveOnlyForLocalLikeOnlyForRemote: "僅限非敏感(遠端僅限按讚)" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "僅限非敏感(遠端僅限讚)" rolesAssignedToMe: "指派給自己的角色" resetPasswordConfirm: "重設密碼?" sensitiveWords: "敏感詞" @@ -1087,6 +1103,7 @@ prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍 hiddenTags: "隱藏標籤" hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。" notesSearchNotAvailable: "無法使用搜尋貼文功能。" +usersSearchNotAvailable: "無法使用使用者搜尋功能。" license: "授權" unfavoriteConfirm: "要取消收錄我的最愛嗎?" myClips: "我的摘錄" @@ -1161,6 +1178,7 @@ installed: "已安裝" branding: "品牌宣傳" enableServerMachineStats: "公佈伺服器的機器資訊" enableIdenticonGeneration: "啟用生成使用者的 Identicon " +showRoleBadgesOfRemoteUsers: "顯示授予遠端使用者的角色徽章" turnOffToImprovePerformance: "關閉時會提高性能。" createInviteCode: "建立邀請碼" createWithOptions: "使用選項建立" @@ -1237,9 +1255,8 @@ showAvatarDecorations: "顯示頭像裝飾" releaseToRefresh: "放開以更新內容" refreshing: "載入更新中" pullDownToRefresh: "往下拉來更新內容" -disableStreamingTimeline: "停用時間軸的即時更新" useGroupedNotifications: "分組顯示通知訊息" -signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" +emailVerificationFailedError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。" doReaction: "做出反應" code: "程式碼" @@ -1309,6 +1326,8 @@ availableRoles: "可用角色" acknowledgeNotesAndEnable: "了解注意事項後再開啟。" federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。" federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。" +draft: "草稿\n" +draftsAndScheduledNotes: "草稿與排定發布" confirmOnReact: "在做出反應前先確認" reactAreYouSure: "用「 {emoji} 」反應嗎?" markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?" @@ -1326,6 +1345,7 @@ restore: "還原" syncBetweenDevices: "裝置之間的同步化" preferenceSyncConflictTitle: "伺服器上存在設定值" preferenceSyncConflictText: "已啟用同步的設定項目會將設定值儲存至伺服器,並已找到該設定項目在伺服器上儲存的設定值。請選擇要使用哪個設定值進行覆寫。" +preferenceSyncConflictChoiceMerge: "合併至" preferenceSyncConflictChoiceServer: "伺服器設定值" preferenceSyncConflictChoiceDevice: "裝置的設定值" preferenceSyncConflictChoiceCancel: "取消啟用同步" @@ -1335,6 +1355,8 @@ postForm: "發文視窗" textCount: "字數" information: "關於" chat: "聊天" +directMessage: "直接訊息" +directMessage_short: "訊息" migrateOldSettings: "遷移舊設定資訊" migrateOldSettings_description: "通常情況下,這會自動進行,但若因某些原因未能順利遷移,您可以手動觸發遷移處理。請注意,當前的設定資訊將會被覆寫。" compress: "壓縮" @@ -1344,7 +1366,101 @@ top: "上" embed: "嵌入" settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「設定 → 其他 → 舊設定資訊移轉」中手動進行移轉)" readonly: "唯讀" +goToDeck: "回到多欄模式" +federationJobs: "聯邦通訊作業" +driveAboutTip: "在「雲端硬碟」中,會顯示過去上傳的檔案列表。
\n可以在附加到貼文時重新利用,或者事先上傳之後再用於發布。
\n請注意,刪除檔案後,之前使用過該檔案的所有地方(貼文、頁面、大頭貼、橫幅等)也會一併無法顯示。
\n也可以建立資料夾來整理檔案。" +scrollToClose: "用滾輪關閉" +advice: "建議" +realtimeMode: "即時模式" +turnItOn: "開啟" +turnItOff: "關閉" +emojiMute: "表情符號靜音" +emojiUnmute: "表情符號解除靜音" +muteX: "將 {x} 靜音" +unmuteX: "將 {x} 解除靜音" +abort: "取消" +tip: "提示與技巧" +redisplayAllTips: "重新顯示所有「提示與技巧」" +hideAllTips: "隱藏所有「提示與技巧」" +defaultImageCompressionLevel: "預設的影像壓縮程度" +defaultImageCompressionLevel_description: "低的話可以保留畫質,但是會增加檔案的大小。
高的話可以減少檔案大小,但是會降低畫質。" +defaultCompressionLevel: "預設的壓縮程度" +defaultCompressionLevel_description: "低的話可以保留品質,但是會增加檔案的大小。
高的話可以減少檔案大小,但是會降低品質。" +inMinutes: "分鐘" +inDays: "日" +safeModeEnabled: "啟用安全模式" +pluginsAreDisabledBecauseSafeMode: "由於啟用安全模式,所有的外掛都被停用。" +customCssIsDisabledBecauseSafeMode: "由於啟用安全模式,所有的客製 CSS 都被停用。" +themeIsDefaultBecauseSafeMode: "在安全模式啟用期間將使用預設主題。關閉安全模式後會恢復原本的設定。" +thankYouForTestingBeta: "感謝您協助驗證 beta 版!" +createUserSpecifiedNote: "建立指定使用者的貼文" +schedulePost: "排定發布" +scheduleToPostOnX: "排定在 {x} 發布" +scheduledToPostOnX: "已排定在 {x} 發布貼文" +schedule: "排定" +scheduled: "排定" +widgets: "小工具" +deviceInfo: "硬體資訊" +deviceInfoDescription: "在提出技術性諮詢時,若能同時提供以下資訊,將有助於解決問題。" +youAreAdmin: "您是管理員" +frame: "邊框" +presets: "預設值" +zeroPadding: "補零" +nothingToConfigure: "無可設定的項目" +_imageEditing: + _vars: + caption: "檔案標題" + filename: "檔案名稱" + filename_without_ext: "無副檔名的檔案名稱" + year: "拍攝年份" + month: "拍攝月份" + day: "拍攝日期" + hour: "拍攝時間(小時)" + minute: "拍攝時間(分鐘)" + second: "拍攝時間(秒)" + camera_model: "相機名稱" + camera_lens_model: "鏡頭型號" + camera_mm: "焦距" + camera_mm_35: "焦距(換算為 35mm 底片等效焦距)" + camera_f: "光圈" + camera_s: "快門速度" + camera_iso: "ISO 感光度" + gps_lat: "緯度" + gps_long: "經度" +_imageFrameEditor: + title: "編輯邊框" + tip: "可以在圖片上添加包含邊框或 EXIF 的標籤來裝飾圖片。" + header: "標題" + footer: "頁尾" + borderThickness: "邊框寬度" + labelThickness: "標籤寬度" + labelScale: "標籤縮放比例" + centered: "置中對齊" + captionMain: "標題文字(大)" + captionSub: "標題文字(小)" + availableVariables: "可使用的變數" + withQrCode: "二維條碼" + backgroundColor: "背景顏色" + textColor: "文字顏色" + font: "字型" + fontSerif: "襯線體" + fontSansSerif: "無襯線體" + quitWithoutSaveConfirm: "不儲存就退出嗎?" + failedToLoadImage: "圖片載入失敗" +_compression: + _quality: + high: "高品質" + medium: "中品質" + low: "低品質" + _size: + large: "大" + medium: "中" + small: "小" +_order: + newest: "最新的在前" + oldest: "最舊的在前" _chat: + messages: "訊息" noMessagesYet: "尚無訊息" newMessage: "新訊息" individualChat: "ㄧ對一聊天室" @@ -1377,6 +1493,8 @@ _chat: chatNotAvailableInOtherAccount: "對方的帳號無法使用聊天功能。" cannotChatWithTheUser: "無法與此使用者聊天" cannotChatWithTheUser_description: "聊天功能目前無法使用,或對方尚未開放聊天功能。" + youAreNotAMemberOfThisRoomButInvited: "您不是此聊天室的參與者,但已收到邀請。若要加入,請先接受邀請。\n" + doYouAcceptInvitation: "您要接受這個邀請嗎?\n" chatWithThisUser: "聊天" thisUserAllowsChatOnlyFromFollowers: "此使用者僅接受來自追隨者的聊天訊息。" thisUserAllowsChatOnlyFromFollowing: "此使用者僅接受自己追隨的使用者傳送聊天訊息。" @@ -1416,10 +1534,26 @@ _settings: makeEveryTextElementsSelectable: "允許選取所有文字" makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。" useStickyIcons: "使大頭貼跟隨捲動" + enableHighQualityImagePlaceholders: "顯示高品質的圖片預覽圖" + uiAnimations: "使用者介面的動畫效果\n" showNavbarSubButtons: "在導覽列顯示輔助按鈕" ifOn: "開啟時" ifOff: "關閉時" enableSyncThemesBetweenDevices: "在裝置之間同步已安裝的主題" + enablePullToRefresh: "下拉更新" + enablePullToRefresh_description: "使用滑鼠,按下並拖曳滾輪。" + realtimeMode_description: "已與伺服器建立連線,將即時更新內容。這可能會增加資料傳輸量與電池消耗。\n" + contentsUpdateFrequency: "內容取得頻率" + contentsUpdateFrequency_description: "頻率越高,內容更新越即時,但可能會降低效能,並增加資料傳輸量與電池消耗。\n" + contentsUpdateFrequency_description2: "當即時模式開啟時,不論此設定為何,內容都會即時更新。" + showUrlPreview: "顯示網址預覽" + showAvailableReactionsFirstInNote: "將可用的反應顯示在頂部" + showPageTabBarBottom: "在底部顯示頁面的標籤列" + emojiPaletteBanner: "可以將固定顯示在表情符號選擇器的預設項目註冊為調色盤,或者自訂選擇器的顯示方式。" + enableAnimatedImages: "啟用動畫圖片" + settingsPersistence_title: "設定的持久化" + settingsPersistence_description1: "啟用「設定的持久化」後,可以防止設定資訊遺失。" + settingsPersistence_description2: "依環境不同,可能無法啟用。" _chat: showSenderName: "顯示發送者的名稱" sendOnEnter: "按下 Enter 發送訊息" @@ -1427,6 +1561,9 @@ _preferencesProfile: profileName: "設定檔案名稱" profileNameDescription: "設定一個名稱來識別此裝置。" profileNameDescription2: "例如:「主要個人電腦」、「智慧型手機」等" + manageProfiles: "管理個人檔案" + shareSameProfileBetweenDevicesIsNotRecommended: "不建議在多個裝置上共用同一個設定檔。" + useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "如果您希望在多個裝置之間同步某些設定項目,請分別啟用「跨裝置同步」選項。" _preferencesBackup: autoBackup: "自動備份" restoreFromBackup: "從備份還原" @@ -1436,6 +1573,7 @@ _preferencesBackup: youNeedToNameYourProfileToEnableAutoBackup: "要啟用自動備份,必須設定檔案名稱。" autoPreferencesBackupIsNotEnabledForThisDevice: "此裝置未啟用自動備份設定。" backupFound: "找到設定的備份" + forceBackup: "強制備份設定" _accountSettings: requireSigninToViewContents: "須登入以顯示內容" requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。" @@ -1465,6 +1603,7 @@ _delivery: manuallySuspended: "手動暫停中" goneSuspended: "因為伺服器刪除所以暫停中" autoSuspendedForNotResponding: "因為伺服器沒有回應所以暫停中" + softwareSuspended: "此軟體因已停止發佈,目前無法使用" _bubbleGame: howToPlay: "玩法說明" hold: "保留" @@ -1502,7 +1641,7 @@ _initialAccountSetting: theseSettingsCanEditLater: "這裡的設定可以在之後變更。" youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。" - pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自{name}的通知了。" + pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自 {name} 的通知了。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" youCanContinueTutorial: "您可以繼續學習如何使用{name}(Misskey),也可以就此打住,立即開始使用。" @@ -1591,11 +1730,37 @@ _serverSettings: fanoutTimelineDbFallback: "資料庫的回退" fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。" reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是,Redis 記憶體使用量會增加。" + remoteNotesCleaning: "自動清除遠端發佈內容" + remoteNotesCleaning_description: "啟用後,系統會定期清理未被參照的舊遠端貼文,以抑制資料庫的膨脹。" + remoteNotesCleaningMaxProcessingDuration: "清理作業的最長持續時間" + remoteNotesCleaningExpiryDaysForEachNotes: "貼文最短保留天數" inquiryUrl: "聯絡表單網址" inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。" openRegistration: "允許建立帳戶" openRegistrationWarning: "開放註冊伴隨著風險。 建議只有在伺服器受到持續監控,並準備好在出現問題時能立即處理的情況下才開放註冊。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "如果在一段期間內沒有偵測到任何審查員活動,此設定將自動關閉,以防止垃圾內容。" + deliverSuspendedSoftware: "已停止發佈的軟體" + deliverSuspendedSoftwareDescription: "由於脆弱性等原因,可以指定伺服器軟體的名稱與版本範圍來停止其發佈。這些版本資訊是由伺服器所提供,其可靠性無法保證。版本的指定可以使用 semver(語意化版本控制) 的範圍語法,但如果指定為 >= 2024.3.1,則像 2024.3.1-custom.0 這樣的自訂版本將不會被包含在內,因此建議使用 >= 2024.3.1-0 的方式來同時包含預發佈版本。" + singleUserMode: "單人模式" + singleUserMode_description: "如果只有自己使用此伺服器的話,啟用此模式將使效能最佳化。" + signToActivityPubGet: "簽署 GET 請求" + signToActivityPubGet_description: "通常應該啟用此功能。停用可能會改善聯邦通訊的問題,但反過來也可能會使某些伺服器無法通訊。" + proxyRemoteFiles: "代理提供遠端檔案" + proxyRemoteFiles_description: "啟用時,它會代理並提供遠端檔案。 這有助於產生影像縮圖和保護使用者隱私。" + allowExternalApRedirect: "允許透過 ActivityPub 查詢時進行重新導向" + allowExternalApRedirect_description: "啟用後,其他伺服器可以透過此伺服器查詢第三方的內容,但也可能導致內容遭到冒充的風險。" + userGeneratedContentsVisibilityForVisitor: "使用者建立的內容對訪客的公開範圍" + userGeneratedContentsVisibilityForVisitor_description: "這有助於防止一些問題的發生,例如未經適當審核的不適當遠端內容無意中透過您自己的伺服器發佈到網際網路上。" + userGeneratedContentsVisibilityForVisitor_description2: "包括伺服器接收到的遠端內容在內,無條件地將伺服器內所有內容公開到網際網路上是具有風險的。特別是對於不了解分散式架構特性的瀏覽者來說,他們可能會誤以為這些遠端內容是由該伺服器所創建的,因此需要特別留意。" + restartServerSetupWizardConfirm_title: "要重新執行伺服器的初始設定精靈嗎?" + restartServerSetupWizardConfirm_text: "當前的部分設定將會被重設。" + entrancePageStyle: "入口頁面的樣式" + showTimelineForVisitor: "顯示時間軸" + showActivitiesForVisitor: "顯示活動" + _userGeneratedContentsVisibilityForVisitor: + all: "全部公開\n" + localOnly: "僅公開本地內容,遠端內容則不公開\n" + none: "全部不公開" _accountMigration: moveFrom: "從其他帳戶遷移到這個帳戶" moveFromSub: "為另一個帳戶建立別名" @@ -1913,6 +2078,8 @@ _role: canManageCustomEmojis: "管理自訂表情符號" canManageAvatarDecorations: "管理頭像裝飾" driveCapacity: "雲端硬碟容量" + maxFileSize: "可上傳的最大檔案大小" + maxFileSize_caption: "前端可能還有其他設定值,例如反向代理或 CDN。" alwaysMarkNsfw: "總是將檔案標記為NSFW" canUpdateBioMedia: "允許更新大頭貼和橫幅" pinMax: "置頂貼文的最大數量" @@ -1927,6 +2094,7 @@ _role: descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "不顯示廣告" canSearchNotes: "可否搜尋貼文" + canSearchUsers: "可使用使用者搜尋功能" canUseTranslator: "使用翻譯功能" avatarDecorationLimit: "頭像可掛上的最大裝飾數量" canImportAntennas: "允許匯入天線" @@ -1935,6 +2103,12 @@ _role: canImportMuting: "允許匯入靜音名單" canImportUserLists: "允許匯入清單" chatAvailability: "允許聊天" + uploadableFileTypes: "可上傳的檔案類型" + uploadableFileTypes_caption: "請指定 MIME 類型。可以用換行區隔多個類型,也可以使用星號(*)作為萬用字元進行指定。(例如:image/*)\n" + uploadableFileTypes_caption2: "有些檔案可能無法判斷其類型。若要允許這類檔案,請在指定中加入 {x}。" + noteDraftLimit: "伺服器端可建立的貼文草稿數量上限\n" + scheduledNoteLimit: "同時建立的排定發布數量" + watermarkAvailable: "浮水印功能是否可用" _condition: roleAssignedTo: "手動指派角色完成" isLocal: "本地使用者" @@ -2094,6 +2268,7 @@ _theme: install: "安裝佈景主題" manage: "管理佈景主題" code: "佈景主題代碼" + copyThemeCode: "複製主題代碼" description: "描述" installed: "{name}已安裝" installedThemes: "已經安裝的佈景主題" @@ -2152,9 +2327,8 @@ _theme: buttonBg: "按鈕背景" buttonHoverBg: "按鈕背景 (漂浮)" inputBorder: "輸入框邊框" - driveFolderBg: "雲端硬碟文件夾背景" badge: "徽章" - messageBg: "私訊背景" + messageBg: "聊天的背景" fgHighlighted: "突顯文字" _sfx: note: "貼文" @@ -2194,6 +2368,7 @@ _time: minute: "分鐘" hour: "小時" day: "日" + month: "個月" _2fa: alreadyRegistered: "此裝置已被註冊過了" registerTOTP: "開始設定驗證應用程式" @@ -2323,6 +2498,7 @@ _auth: scopeUser: "以下列使用者身分操作" pleaseLogin: "必須登入以提供應用程式的存取權限。" byClickingYouWillBeRedirectedToThisUrl: "如果授予存取權限,就會自動導向到以下的網址" + alreadyAuthorized: "此應用程式已被授予存取權限。" _antennaSources: all: "全部貼文" homeTimeline: "來自已追隨使用者的貼文" @@ -2369,6 +2545,44 @@ _widgets: clicker: "點擊器" birthdayFollowings: "今天生日的使用者" chat: "聊天" +_widgetOptions: + showHeader: "檢視標頭 " + transparent: "使背景透明" + height: "高度" + _button: + colored: "彩色" + _clock: + size: "尺寸" + thickness: "指針粗細" + thicknessThin: "細" + thicknessMedium: "普通" + thicknessThick: "粗" + graduations: "刻度盤" + graduationDots: "圓點" + graduationArabic: "阿拉伯數字" + fadeGraduations: "刻度淡出" + sAnimation: "秒針的動畫效果" + sAnimationElastic: "真實的" + sAnimationEaseOut: "滑順" + twentyFour: "24 小時制" + labelTime: "時間" + labelTz: "時區" + labelTimeAndTz: "時間與時區" + timezone: "時區" + showMs: "顯示毫秒" + showLabel: "顯示標記" + _jobQueue: + sound: "播放音效" + _rss: + url: "RSS 訂閱網址" + refreshIntervalSec: "更新間隔(秒)" + maxEntries: "最大顯示數量" + _rssTicker: + shuffle: "顯示順序隨機排列" + duration: "RSS 跑馬燈的捲動速度(秒)" + reverse: "反方向滾動" + _birthdayFollowings: + period: "時長" _cw: hide: "隱藏" show: "顯示內容" @@ -2408,9 +2622,25 @@ _visibility: disableFederation: "停用聯邦" disableFederationDescription: "不發送到其他伺服器" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "尚有未上傳的檔案,確定要放棄並關閉表單嗎?" + uploaderTip: "檔案尚未上傳。您可以從檔案選單中設定重新命名、裁切圖片、加上浮水印、是否壓縮等選項。檔案會在發布貼文時自動上傳。\n" 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: "可以進行其他操作,例如儲存為草稿、預約發佈貼文、或設定反應等。\n" + submit_title: "貼文按鈕" + submit_description: "發布貼文。也可以使用 Ctrl + Enter 或 Cmd + Enter 來發布。" _placeholders: a: "今天過得如何?" b: "有什麼新鮮事嗎?" @@ -2520,7 +2750,7 @@ _pages: hideTitleWhenPinned: "被置頂於個人資料時隱藏頁面標題" font: "字型" fontSerif: "襯線體" - fontSansSerif: "黑體" + fontSansSerif: "無襯線體" eyeCatchingImageSet: "設定封面影像" eyeCatchingImageRemove: "刪除封面影像" chooseBlock: "新增方塊" @@ -2556,6 +2786,8 @@ _notification: youReceivedFollowRequest: "您有新的追隨請求" yourFollowRequestAccepted: "您的追隨請求已被核准" pollEnded: "問卷調查已產生結果" + scheduledNotePosted: "已排定發布貼文" + scheduledNotePostFailed: "排定發布貼文失敗了" newNote: "新的貼文" unreadAntennaNote: "天線 {name}" roleAssigned: "已授予角色" @@ -2585,6 +2817,8 @@ _notification: quote: "引用" reaction: "反應" pollEnded: "問卷調查結束" + scheduledNotePosted: "預約發佈成功" + scheduledNotePostFailed: "預約發佈失敗" receiveFollowRequest: "已收到追隨請求" followRequestAccepted: "追隨請求已接受" roleAssigned: "已授予角色" @@ -2624,6 +2858,14 @@ _deck: usedAsMinWidthWhenFlexible: "如果啟用「自動調整寬度」,此為最小寬度" flexible: "自動調整寬度" enableSyncBetweenDevicesForProfiles: "啟用裝置與裝置之間的設定檔資料同步化" + showHowToUse: "檢視使用者界面說明" + _howToUse: + addColumn_title: "新增欄位" + addColumn_description: "您可以選擇要新增的欄位類型。" + settings_title: "使用者界面設定" + settings_description: "您可以對多欄模式使用者界面做詳細設定。" + switchProfile_title: "切換設定檔" + switchProfile_description: "將使用者界面佈局儲存為設定檔,就可以隨時切換使用。" _columns: main: "主列" widgets: "小工具" @@ -2684,6 +2926,8 @@ _abuseReport: notifiedWebhook: "使用的 Webhook" deleteConfirm: "確定要刪除通知對象嗎?" _moderationLogTypes: + clearQueue: "清除佇列" + promoteQueue: "重新嘗試排程中的工作" createRole: "新增角色" deleteRole: "刪除角色 " updateRole: "更新角色設定" @@ -2741,6 +2985,7 @@ _fileViewer: url: "URL" uploadedAt: "加入日期" attachedNotes: "含有附件的貼文" + usage: "使用情況" thisPageCanBeSeenFromTheAuthor: "本頁面僅限上傳了這個檔案的使用者可以檢視。" _externalResourceInstaller: title: "從外部網站安裝" @@ -2788,9 +3033,12 @@ _dataSaver: _avatar: title: "大頭貼" description: "停止顯示大頭貼的動畫。由於動畫圖片的檔案大小可能比普通圖片大,這可以進一步減少資料流量。" - _urlPreview: - title: "網址預覽縮圖" + _urlPreviewThumbnail: + title: "不顯示網址預覽縮圖" description: "將不再自動載入網址預覽縮圖。" + _disableUrlPreview: + title: "停用網址預覽" + description: "停用網址預覽功能。與單獨使用縮圖不同,這樣可以減少載入連結資訊本身。" _code: title: "程式碼突出顯示" description: "如果使用了程式碼突顯語法(如 MFM),則在點擊之前不會被載入。由於需要為對應的程式語言下載突顯定義檔案,因此關閉自動載入有助於減少資料流量。" @@ -2848,6 +3096,8 @@ _offlineScreen: _urlPreviewSetting: title: "URL 預覽設定" enable: "啟用 URL 預覽" + allowRedirect: "允許預覽目標的重新導向" + allowRedirectDescription: "設定當輸入的 URL 發生重新導向時,是否追蹤該重新導向並顯示預覽。若停用此功能,雖可節省伺服器資源,但將無法顯示重新導向後的內容。\n" timeout: "取得預覽的逾時時間 (ms)" timeoutDescription: "若取得預覽所需的時間超過這個值,則不會產生預覽。" maximumContentLength: "Content-Length 的最大値 (byte)" @@ -2921,10 +3171,6 @@ _customEmojisManager: uploadSettingDescription: "您可以在此畫面設定表情符號上傳時的操作。" directoryToCategoryLabel: "在「類別」欄位中輸入目錄名稱" directoryToCategoryCaption: "拖放目錄時,請在「類別」欄位中輸入目錄名稱。" - emojiInputAreaCaption: "以下列其中一種方式選擇您想要註冊的表情符號" - emojiInputAreaList1: "將圖片檔案或目錄拖放到此框中" - emojiInputAreaList2: "點擊此連結從電腦中選擇" - emojiInputAreaList3: "點擊此連結從雲端硬碟中選擇" confirmRegisterEmojisDescription: "將列表中顯示的表情符號登錄為新的自定表情符號。是否確定?(為避免過高負荷,每次操作最多可登錄{count}個表情符號)" confirmClearEmojisDescription: "放棄編輯內容並清除列表中顯示的表情符號。是否確定?" confirmUploadEmojisDescription: "將拖放的{count}個檔案上傳到雲端硬碟。是否執行此操作?" @@ -2992,6 +3238,7 @@ _bootErrors: otherOption1: "刪除用戶端設定和快取" otherOption2: "啟動簡易用戶端" otherOption3: "啟動修復工具" + otherOption4: "以安全模式啟動 Misskey" _search: searchScopeAll: "全部" searchScopeLocal: "本地" @@ -3000,3 +3247,196 @@ _search: pleaseEnterServerHost: "請輸入伺服器的主機名稱" pleaseSelectUser: "請選擇使用者" serverHostPlaceholder: "例:misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey 的安裝已經完成了!" + firstCreateAccount: "首先,請建立管理者帳戶。" + accountCreated: "已建立管理者帳戶!" + serverSetting: "伺服器設定" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "利用這個精靈,可以簡單地最佳化伺服器的設定。" + settingsYouMakeHereCanBeChangedLater: "這裡的設定之後也可以進行更改。\n" + howWillYouUseMisskey: "您打算如何使用 Misskey?\n" + _use: + single: "單人伺服器" + single_description: "作為自己專用的伺服器,單獨使用。\n" + single_youCanCreateMultipleAccounts: "即使作為單人伺服器運行,根據需要也可以創建多個帳戶。\n" + group: "群組伺服器\n" + group_description: "邀請可信賴的其他使用者,共同使用伺服器。\n" + open: "開放式伺服器" + open_description: "運營時接納不特定多數的使用者。" + openServerAdvice: "接納不特定多數使用者會帶來風險。為了能夠有效處理問題,建議建立完善的審查機制來進行運營。\n" + openServerAntiSpamAdvice: "為了防止自家伺服器成為垃圾郵件的跳板,必須啟用如 reCAPTCHA 等反機器人功能,並對安全性保持高度警覺。\n" + howManyUsersDoYouExpect: "您預計有多少人使用呢?\n" + _scale: + small: "100人以下(小規模)\n" + medium: "100人以上1000人以下(中規模)\n" + large: "1000人以上(大規模)\n" + largeScaleServerAdvice: "在大規模伺服器中,可能需要具備高階基礎設施知識,如負載平衡和資料庫複寫等。\n" + doYouConnectToFediverse: "您要連接到聯邦宇宙(Fediverse)嗎?\n" + doYouConnectToFediverse_description1: "連接到由分散型伺服器構成的網絡(聯邦宇宙)後,您可以與其他伺服器進行內容的互相交流。\n" + doYouConnectToFediverse_description2: "連接到聯邦宇宙被稱為「聯邦」。\n" + youCanConfigureMoreFederationSettingsLater: "您可以在稍後進行更高級的設定,例如指定可以聯繫的伺服器等。\n" + remoteContentsCleaning: "自動清理接收的內容" + remoteContentsCleaning_description: "進行聯邦後,會持續接收大量內容。啟用自動清理功能後,系統會自動從伺服器中刪除未被參照的過時內容,以節省儲存空間。" + adminInfo: "管理員資訊" + adminInfo_description: "設定用於接收查詢的管理者資訊。\n" + adminInfo_mustBeFilled: "當設置為開放伺服器或啟用聯邦時,必須填寫此資訊。\n" + followingSettingsAreRecommended: "建議使用下列設定" + applyTheseSettings: "套用此設定" + skipSettings: "跳過設定" + settingsCompleted: "設定完成!" + settingsCompleted_description: "辛苦了!準備已經完成,您可以立即開始使用伺服器了。\n" + settingsCompleted_description2: "詳細的伺服器設定可透過「控制臺」進行。" + donationRequest: "請求捐款" + _donationRequest: + text1: "Misskey 是由志願者開發的免費軟體。" + text2: "為了能夠繼續開發,若您願意的話,請考慮進行捐款。\n" + text3: "也有提供支援者專屬的特典!\n" +_uploader: + editImage: "編輯圖片" + compressedToX: "壓縮為 {x}" + savedXPercent: "節省了 {x}%" + abortConfirm: "有些檔案尚未上傳,您要中止嗎?" + doneConfirm: "有些檔案尚未上傳,是否要完成上傳?" + maxFileSizeIsX: "可上傳的最大檔案大小為 {x}。" + allowedTypes: "可上傳的檔案類型。" + tip: "檔案尚未上傳。您可以在此對話框中進行上傳前的確認、重新命名、壓縮、裁切等操作。準備完成後,請點選「上傳」按鈕開始上傳。\n" +_clientPerformanceIssueTip: + title: "如果覺得電池消耗過快的話" + makeSureDisabledAdBlocker: "請將廣告阻擋器停用" + makeSureDisabledAdBlocker_description: "廣告阻擋器可能會影響效能。請確認作業系統功能、瀏覽器設定或擴充功能中是否啟用了廣告阻擋器。\n" + makeSureDisabledCustomCss: "請停用自訂 CSS" + makeSureDisabledCustomCss_description: "覆蓋樣式可能會影響效能。請確認是否啟用了自訂 CSS 或其他會覆蓋樣式的擴充功能。\n" + makeSureDisabledAddons: "請停用擴充功能" + makeSureDisabledAddons_description: "部分擴充功能可能會干擾用戶端的運作並影響效能。請嘗試停用瀏覽器的擴充功能,以確認是否能改善情況" +_clip: + tip: "摘錄是一項可以用來整理貼文的功能。" +_userLists: + tip: "您可以建立包含任意使用者的清單。建立後的清單可以作為時間軸顯示。\n" +watermark: "浮水印" +defaultPreset: "預設值" +_watermarkEditor: + tip: "可以在圖片中以浮水印加上出處等資訊。" + quitWithoutSaveConfirm: "不儲存就退出嗎?" + driveFileTypeWarn: "不支援此檔案" + driveFileTypeWarnDescription: "請選擇圖片檔案" + title: "編輯浮水印" + cover: "覆蓋整體" + repeat: "佈局" + preserveBoundingRect: "調整使其在旋轉時不會突出" + opacity: "透明度" + scale: "大小" + text: "文字" + qr: "二維條碼" + position: "位置" + margin: "邊界" + type: "類型" + image: "圖片" + advanced: "進階" + angle: "角度" + stripe: "條紋" + stripeWidth: "線條寬度" + stripeFrequency: "線條數量" + polkadot: "波卡圓點" + checker: "棋盤格" + polkadotMainDotOpacity: "主圓點的不透明度" + polkadotMainDotRadius: "主圓點的尺寸" + polkadotSubDotOpacity: "子圓點的不透明度" + polkadotSubDotRadius: "子圓點的尺寸" + polkadotSubDotDivisions: "子圓點的數量" + leaveBlankToAccountUrl: "若留空則使用帳戶的 URL" + failedToLoadImage: "圖片載入失敗" +_imageEffector: + title: "特效" + addEffect: "新增特效" + discardChangesConfirm: "捨棄更改並退出嗎?" + failedToLoadImage: "圖片載入失敗" + _fxs: + chromaticAberration: "色差" + glitch: "異常雜訊效果" + mirror: "鏡像" + invert: "反轉色彩" + grayscale: "黑白" + blur: "模糊" + pixelate: "馬賽克" + colorAdjust: "色彩校正" + colorClamp: "壓縮色彩" + colorClampAdvanced: "壓縮色彩(進階)" + distort: "變形" + threshold: "閾值" + zoomLines: "速度線" + stripe: "條紋" + polkadot: "波卡圓點" + checker: "棋盤格" + blockNoise: "阻擋雜訊" + tearing: "撕裂" + fill: "填充" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + radius: "半徑" + samples: "取樣數" + offset: "位置" + color: "顏色" + opacity: "透明度" + normalize: "正規化" + amount: "數量" + lightness: "亮度" + contrast: "對比度" + hue: "色相" + brightness: "亮度" + saturation: "彩度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "頻率" + strength: "強度" + glitchChannelShift: "偏移" + seed: "種子值" + redComponent: "紅色成分" + greenComponent: "綠色成分" + blueComponent: "青色成分" + threshold: "閾值" + centerX: "X中心座標" + centerY: "Y中心座標" + zoomLinesSmoothing: "平滑化" + zoomLinesSmoothingDescription: "平滑化與集中線寬度設定不能同時使用。" + zoomLinesThreshold: "集中線的寬度" + zoomLinesMaskSize: "中心直徑" + zoomLinesBlack: "變成黑色" + circle: "圓形" +drafts: "草稿\n" +_drafts: + select: "選擇草槁" + cannotCreateDraftAnymore: "已超出可建立的草稿數量上限。\n" + cannotCreateDraft: "無法以此內容建立草稿。\n" + delete: "刪除草稿" + deleteAreYouSure: "確定要刪除草稿嗎?\n" + noDrafts: "沒有草稿。\n" + replyTo: "回覆給 {user}\n" + quoteOf: "引用自 {user} 的貼文\n" + postTo: "發佈到 {channel}\n" + saveToDraft: "儲存為草稿" + restoreFromDraft: "從草稿復原\n" + restore: "還原" + listDrafts: "草稿清單" + schedule: "排定發布" + listScheduledNotes: "排定發布列表" + cancelSchedule: "解除排定" +qr: "二維條碼" +_qr: + showTabTitle: "檢視" + readTabTitle: "讀取" + shareTitle: "{name} {acct}" + shareText: "請在聯邦宇宙追隨我吧!" + chooseCamera: "選擇相機" + cannotToggleFlash: "無法切換閃光燈" + turnOnFlash: "開啟閃光燈" + turnOffFlash: "關閉閃光燈" + startQr: "啟動條碼掃描器" + stopQr: "停止條碼掃描器" + noQrCodeFound: "找不到 QR code" + scanFile: "掃描在裝置上的影像" + raw: "文字" + mfm: "MFM" diff --git a/package.json b/package.json index 1460e936ef..51998fa9cc 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,38 @@ { "name": "misskey", - "version": "2025.4.1-alpha.2", + "version": "2026.1.0-beta.0", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.6.1", + "packageManager": "pnpm@10.27.0", "workspaces": [ - "packages/frontend-shared", - "packages/frontend", - "packages/frontend-embed", - "packages/backend", - "packages/sw", "packages/misskey-js", + "packages/i18n", "packages/misskey-reversi", "packages/misskey-bubble-game", - "packages/misskey-mahjong" + "packages/misskey-mahjong", + "packages/icons-subsetter", + "packages/frontend-shared", + "packages/frontend-builder", + "packages/sw", + "packages/backend", + "packages/frontend", + "packages/frontend-embed" ], "private": true, "scripts": { - "build-pre": "node ./scripts/build-pre.js", + "compile-config": "cd packages/backend && pnpm compile-config", + "build-pre": "node scripts/build-pre.mjs", "build-assets": "node ./scripts/build-assets.mjs", "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", "build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", - "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", - "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", + "start": "cd packages/backend && pnpm compile-config && node ./built/boot/entry.js", + "start:inspect": "cd packages/backend && pnpm compile-config && node --inspect ./built/boot/entry.js", + "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js", + "cli": "cd packages/backend && pnpm cli", "init": "pnpm migrate", "migrate": "cd packages/backend && pnpm migrate", "revert": "cd packages/backend && pnpm revert", @@ -34,8 +40,8 @@ "migrateandstart": "pnpm migrate && pnpm start", "watch": "pnpm dev", "dev": "node scripts/dev.mjs", - "lint": "pnpm -r lint", - "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", + "lint": "pnpm --no-bail -r lint", + "cy:open": "pnpm cypress open --config-file=cypress.config.ts", "cy:run": "pnpm cypress run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", "e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run", @@ -43,39 +49,40 @@ "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "test": "pnpm -r test", "test-and-coverage": "pnpm -r test-and-coverage", - "clean": "node ./scripts/clean.js", - "clean-all": "node ./scripts/clean-all.js", + "clean": "node scripts/clean.mjs", + "clean-all": "node scripts/clean-all.mjs", "cleanall": "pnpm clean-all" }, "resolutions": { - "chokidar": "4.0.3", + "chokidar": "5.0.0", "lodash": "4.17.21" }, "dependencies": { - "cssnano": "7.0.6", - "execa": "9.5.2", - "fast-glob": "3.3.3", - "ignore-walk": "7.0.0", - "js-yaml": "4.1.0", - "postcss": "8.5.3", - "tar": "7.4.3", - "terser": "5.39.0", - "typescript": "5.8.2", - "esbuild": "0.25.0", - "glob": "11.0.1" + "cssnano": "7.1.2", + "esbuild": "0.27.2", + "execa": "9.6.1", + "ignore-walk": "8.0.0", + "js-yaml": "4.1.1", + "postcss": "8.5.6", + "tar": "7.5.2", + "terser": "5.44.1" }, "devDependencies": { - "@misskey-dev/eslint-plugin": "2.1.0", - "@types/node": "22.13.10", - "@typescript-eslint/eslint-plugin": "8.26.0", - "@typescript-eslint/parser": "8.26.0", - "cross-env": "7.0.3", - "cypress": "14.1.0", - "eslint": "9.22.0", - "globals": "16.0.0", + "@eslint/js": "9.39.2", + "@misskey-dev/eslint-plugin": "2.2.0", + "@types/js-yaml": "4.0.9", + "@types/node": "24.10.4", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript/native-preview": "7.0.0-dev.20251226.1", + "cross-env": "10.1.0", + "cypress": "15.8.1", + "eslint": "9.39.2", + "globals": "16.5.0", "ncp": "2.0.0", - "pnpm": "10.6.1", - "start-server-and-test": "2.0.10" + "pnpm": "10.27.0", + "typescript": "5.9.3", + "start-server-and-test": "2.1.3" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.22.0" @@ -84,8 +91,9 @@ "overrides": { "@aiscript-dev/aiscript-languageserver": "-" }, - "patchedDependencies": { - "re2": "scripts/dependency-patches/re2.patch" - } + "ignoredBuiltDependencies": [ + "@sentry-internal/node-cpu-profiler", + "exifreader" + ] } } diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc index f4bf7a4d2a..7e1767a67a 100644 --- a/packages/backend/.swcrc +++ b/packages/backend/.swcrc @@ -3,12 +3,17 @@ "jsc": { "parser": { "syntax": "typescript", + "jsx": true, "dynamicImport": true, "decorators": true }, "transform": { "legacyDecorator": true, - "decoratorMetadata": true + "decoratorMetadata": true, + "react": { + "runtime": "automatic", + "importSource": "@kitajs/html" + } }, "experimental": { "keepImportAssertions": true diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/assets/misc/bios.css similarity index 100% rename from packages/backend/src/server/web/bios.css rename to packages/backend/assets/misc/bios.css diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/assets/misc/bios.js similarity index 98% rename from packages/backend/src/server/web/bios.js rename to packages/backend/assets/misc/bios.js index 9ff5dca72a..f9716d8f00 100644 --- a/packages/backend/src/server/web/bios.js +++ b/packages/backend/assets/misc/bios.js @@ -9,7 +9,7 @@ window.onload = async () => { const account = JSON.parse(localStorage.getItem('account')); const i = account.token; - const api = (endpoint, data = {}) => { + const _api = (endpoint, data = {}) => { const promise = new Promise((resolve, reject) => { // Append a credential if (i) data.i = i; diff --git a/packages/backend/src/server/web/cli.css b/packages/backend/assets/misc/cli.css similarity index 100% rename from packages/backend/src/server/web/cli.css rename to packages/backend/assets/misc/cli.css diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/assets/misc/cli.js similarity index 100% rename from packages/backend/src/server/web/cli.js rename to packages/backend/assets/misc/cli.js diff --git a/packages/backend/src/server/web/error.css b/packages/backend/assets/misc/error.css similarity index 100% rename from packages/backend/src/server/web/error.css rename to packages/backend/assets/misc/error.css diff --git a/packages/backend/src/server/web/error.js b/packages/backend/assets/misc/error.js similarity index 100% rename from packages/backend/src/server/web/error.js rename to packages/backend/assets/misc/error.js diff --git a/packages/backend/assets/misc/flush.js b/packages/backend/assets/misc/flush.js new file mode 100644 index 0000000000..991b8ea808 --- /dev/null +++ b/packages/backend/assets/misc/flush.js @@ -0,0 +1,46 @@ +(async () => { + const msg = document.getElementById('msg'); + const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`; + + if (!document.cookie) { + message('Your site data is fully cleared by your browser.'); + message(successText); + } else { + message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.'); + try { + localStorage.clear(); + message('localStorage cleared.'); + + const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { + const delidb = indexedDB.deleteDatabase(name); + delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); + delidb.onerror = e => rej(e) + })); + + await Promise.all(idbPromises); + + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage('clear'); + await navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }) + .catch(e => { throw new Error(e) }); + } + + message(successText); + } catch (e) { + message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); + message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) + + console.error(e); + setTimeout(() => { + location = '/'; + }, 10000) + } + } + + function message(text) { + msg.insertAdjacentHTML('beforeend', `

[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}

`) + } +})(); diff --git a/packages/backend/assets/misc/info-card.css b/packages/backend/assets/misc/info-card.css new file mode 100644 index 0000000000..3e27223cc5 --- /dev/null +++ b/packages/backend/assets/misc/info-card.css @@ -0,0 +1,35 @@ +html, +body { + margin: 0; + padding: 0; + min-height: 100vh; + background: #fff; +} + +#a { + display: block; +} + +#banner { + background-size: cover; + background-position: center center; +} + +#title { + display: inline-block; + margin: 24px; + padding: 0.5em 0.8em; + color: #fff; + background: rgba(0, 0, 0, 0.5); + font-weight: bold; + font-size: 1.3em; +} + +#content { + overflow: auto; + color: #353c3e; +} + +#description { + margin: 24px; +} diff --git a/packages/backend/build.js b/packages/backend/build.js new file mode 100644 index 0000000000..52ca09b7a8 --- /dev/null +++ b/packages/backend/build.js @@ -0,0 +1,121 @@ +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { build } from 'esbuild'; +import { swcPlugin } from 'esbuild-plugin-swc'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); +const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8')); + +const resolveTsPathsPlugin = { + name: 'resolve-ts-paths', + setup(build) { + build.onResolve({ filter: /^\.{1,2}\/.*\.js$/ }, (args) => { + if (args.importer) { + const absPath = join(args.resolveDir, args.path); + const tsPath = absPath.slice(0, -3) + '.ts'; + if (fs.existsSync(tsPath)) return { path: tsPath }; + const tsxPath = absPath.slice(0, -3) + '.tsx'; + if (fs.existsSync(tsxPath)) return { path: tsxPath }; + } + }); + }, +}; + +const externalIpaddrPlugin = { + name: 'external-ipaddr', + setup(build) { + build.onResolve({ filter: /^ipaddr\.js$/ }, (args) => { + return { path: args.path, external: true }; + }); + }, +}; + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints: ['./src/boot/entry.ts'], + minify: true, + keepNames: true, + bundle: true, + outdir: './built/boot', + target: 'node22', + platform: 'node', + format: 'esm', + sourcemap: 'linked', + packages: 'external', + banner: { + js: 'import { createRequire as topLevelCreateRequire } from "module";' + + 'import ___url___ from "url";' + + 'const require = topLevelCreateRequire(import.meta.url);' + + 'const __filename = ___url___.fileURLToPath(import.meta.url);' + + 'const __dirname = ___url___.fileURLToPath(new URL(".", import.meta.url));', + }, + plugins: [ + externalIpaddrPlugin, + resolveTsPathsPlugin, + swcPlugin({ + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + dynamicImport: true, + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + }, + experimental: { + keepImportAssertions: true, + }, + baseUrl: join(_dirname, 'src'), + paths: { + '@/*': ['*'], + }, + target: 'esnext', + keepClassNames: true, + }, + }), + externalIpaddrPlugin, + ], + // external: [ + // 'slacc-*', + // 'class-transformer', + // 'class-validator', + // '@sentry/*', + // '@nestjs/websockets/socket-module', + // '@nestjs/microservices/microservices-module', + // '@nestjs/microservices', + // '@napi-rs/canvas-win32-x64-msvc', + // 'mock-aws-s3', + // 'aws-sdk', + // 'nock', + // 'sharp', + // 'jsdom', + // 're2', + // '@napi-rs/canvas', + // ], +}; + +const args = process.argv.slice(2).map(arg => arg.toLowerCase()); + +if (!args.includes('--no-clean')) { + fs.rmSync('./built', { recursive: true, force: true }); +} + +await buildSrc(); + +async function buildSrc() { + console.log(`[${_package.name}] start building...`); + + await build(options) + .then(() => { + console.log(`[${_package.name}] build succeeded.`); + }) + .catch((err) => { + process.stderr.write(err.stderr || err.message || err); + process.exit(1); + }); + + console.log(`[${_package.name}] finish building.`); +} diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js index ae7b2baf49..d15a703ba2 100644 --- a/packages/backend/eslint.config.js +++ b/packages/backend/eslint.config.js @@ -1,4 +1,5 @@ import tsParser from '@typescript-eslint/parser'; +import globals from 'globals'; import sharedConfig from '../shared/eslint.config.js'; export default [ @@ -6,6 +7,13 @@ export default [ { ignores: ['**/node_modules', 'built', '@types/**/*', 'migration'], }, + { + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, { files: ['**/*.ts', '**/*.tsx'], languageOptions: { diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs index 5a4aa4e15a..22ffbbee5c 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.cjs @@ -205,7 +205,7 @@ module.exports = { // Whether to use watchman for file crawling // watchman: true, - extensionsToTreatAsEsm: ['.ts'], + extensionsToTreatAsEsm: ['.ts', '.tsx'], testTimeout: 60000, 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/jest.js b/packages/backend/jest.js new file mode 100644 index 0000000000..61f6b00e85 --- /dev/null +++ b/packages/backend/jest.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node +import child_process from 'node:child_process'; +import path from 'node:path'; +import url from 'node:url'; + +import semver from 'semver'; + +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const args = []; +args.push(...[ + ...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'), + ...process.argv.slice(2), +]); + +const child = child_process.spawn(process.execPath, args, { stdio: 'inherit' }); +child.on('error', (err) => { + console.error('Failed to start Jest:', err); + process.exit(1); +}); +child.on('exit', (code, signal) => { + if (code === null) { + process.exit(128 + signal); + } else { + process.exit(code); + } +}); diff --git a/packages/backend/jsconfig.json b/packages/backend/jsconfig.json deleted file mode 100644 index 1230aadd12..0000000000 --- a/packages/backend/jsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "allowSyntheticDefaultImports": true - }, - "exclude": [ - "node_modules", - "jspm_packages", - "tmp", - "temp" - ] -} diff --git a/packages/backend/migration/1736686850345-createNoteDraft.js b/packages/backend/migration/1736686850345-createNoteDraft.js new file mode 100644 index 0000000000..3b525a7339 --- /dev/null +++ b/packages/backend/migration/1736686850345-createNoteDraft.js @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class CreateNoteDraft1736686850345 { + name = 'CreateNoteDraft1736686850345' + + async up(queryRunner) { + await queryRunner.query(` + CREATE TABLE "note_draft" ( + "id" varchar NOT NULL, + "replyId" varchar NULL, + "renoteId" varchar NULL, + "text" text NULL, + "cw" varchar(512) NULL, + "userId" varchar NOT NULL, + "localOnly" boolean DEFAULT false, + "reactionAcceptance" varchar(64) NULL, + "visibility" varchar NOT NULL, + "fileIds" varchar[] DEFAULT '{}', + "visibleUserIds" varchar[] DEFAULT '{}', + "hashtag" varchar(128) NULL, + "channelId" varchar NULL, + "hasPoll" boolean DEFAULT false, + "pollChoices" varchar(256)[] DEFAULT '{}', + "pollMultiple" boolean NULL, + "pollExpiresAt" TIMESTAMP WITH TIME ZONE NULL, + "pollExpiredAfter" bigint NULL, + PRIMARY KEY ("id") + )`); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_REPLY_ID" ON "note_draft" ("replyId") + `); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_RENOTE_ID" ON "note_draft" ("renoteId") + `); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_USER_ID" ON "note_draft" ("userId") + `); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_FILE_IDS" ON "note_draft" USING GIN ("fileIds") + `); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_VISIBLE_USER_IDS" ON "note_draft" USING GIN ("visibleUserIds") + `); + + await queryRunner.query(` + CREATE INDEX "IDX_NOTE_DRAFT_CHANNEL_ID" ON "note_draft" ("channelId") + `); + + await queryRunner.query(` + ALTER TABLE "note_draft" + ADD CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "note_draft" + ADD CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "note_draft" + ADD CONSTRAINT "FK_NOTE_DRAFT_USER_ID" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "note_draft" + ADD CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE + `); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_USER_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_CHANNEL_ID"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_VISIBLE_USER_IDS"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_FILE_IDS"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_USER_ID"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_RENOTE_ID"`); + await queryRunner.query(`DROP INDEX "IDX_NOTE_DRAFT_REPLY_ID"`); + await queryRunner.query(`DROP TABLE "note_draft"`); + } +} diff --git a/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js b/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js new file mode 100644 index 0000000000..19983a72bd --- /dev/null +++ b/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class DeliverSuspendedSoftware1743403874305 { + name = 'DeliverSuspendedSoftware1743403874305' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "deliverSuspendedSoftware" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deliverSuspendedSoftware"`); + } +} diff --git a/packages/backend/migration/1745378064470-composite-note-index.js b/packages/backend/migration/1745378064470-composite-note-index.js new file mode 100644 index 0000000000..576bf7d19a --- /dev/null +++ b/packages/backend/migration/1745378064470-composite-note-index.js @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1'; + +export class CompositeNoteIndex1745378064470 { + name = 'CompositeNoteIndex1745378064470'; + transaction = isConcurrentIndexMigrationEnabled ? false : undefined; + + async up(queryRunner) { + const concurrently = isConcurrentIndexMigrationEnabled; + + if (concurrently) { + const hasValidIndex = await queryRunner.query(`SELECT indisvalid FROM pg_index INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid WHERE pg_class.relname = 'IDX_724b311e6f883751f261ebe378'`); + if (hasValidIndex.length === 0 || hasValidIndex[0].indisvalid !== true) { + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`CREATE INDEX CONCURRENTLY "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); + } + } else { + await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); + } + + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_5b87d9d19127bd5d92026017a7"`); + // Flush all cached Linear Scan Plans and redo statistics for composite index + // this is important for Postgres to learn that even in highly complex queries, using this index first can reduce the result set significantly + await queryRunner.query(`ANALYZE "user", "note"`); + } + + async down(queryRunner) { + const mayConcurrently = isConcurrentIndexMigrationEnabled ? 'CONCURRENTLY' : ''; + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`); + } +} diff --git a/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js b/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js new file mode 100644 index 0000000000..115698a420 --- /dev/null +++ b/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class VisibleUserGeneratedContentsForNonLoggedInVisitors1746330901644 { + name = 'VisibleUserGeneratedContentsForNonLoggedInVisitors1746330901644' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "ugcVisibilityForVisitor" character varying(128) NOT NULL DEFAULT 'local'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "ugcVisibilityForVisitor"`); + } +} diff --git a/packages/backend/migration/1746422049376-singleUserMode.js b/packages/backend/migration/1746422049376-singleUserMode.js new file mode 100644 index 0000000000..9a79d46d5b --- /dev/null +++ b/packages/backend/migration/1746422049376-singleUserMode.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SingleUserMode1746422049376 { + name = 'SingleUserMode1746422049376' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "singleUserMode" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "singleUserMode"`); + } +} diff --git a/packages/backend/migration/1746949539915-migrateSomeConfigFileSettingsToMeta.js b/packages/backend/migration/1746949539915-migrateSomeConfigFileSettingsToMeta.js new file mode 100644 index 0000000000..cb8bb33459 --- /dev/null +++ b/packages/backend/migration/1746949539915-migrateSomeConfigFileSettingsToMeta.js @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + + +export class MigrateSomeConfigFileSettingsToMeta1746949539915 { + name = 'MigrateSomeConfigFileSettingsToMeta1746949539915' + + async up(queryRunner) { + // $1 cannot be used in ALTER TABLE queries + await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT TRUE`); + await queryRunner.query(`ALTER TABLE "meta" ADD "signToActivityPubGet" boolean NOT NULL DEFAULT TRUE`); + await queryRunner.query(`ALTER TABLE "meta" ADD "allowExternalApRedirect" boolean NOT NULL DEFAULT TRUE`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowExternalApRedirect"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "signToActivityPubGet"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyRemoteFiles"`); + } +} diff --git a/packages/backend/migration/1748310233000-addUrlPreviewAllowRedirect.js b/packages/backend/migration/1748310233000-addUrlPreviewAllowRedirect.js new file mode 100644 index 0000000000..a895d0a941 --- /dev/null +++ b/packages/backend/migration/1748310233000-addUrlPreviewAllowRedirect.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddUrlPreviewAllowRedirect1748310233000 { + name = 'AddUrlPreviewAllowRedirect1748310233000' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "urlPreviewAllowRedirect" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "urlPreviewAllowRedirect"`); + } +} diff --git a/packages/backend/migration/1750729939704-FixAvatarUrl.js b/packages/backend/migration/1750729939704-FixAvatarUrl.js new file mode 100644 index 0000000000..81a598136b --- /dev/null +++ b/packages/backend/migration/1750729939704-FixAvatarUrl.js @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +export class FixAvatarUrl1750729939704 { + name = 'FixAvatarUrl1750729939704' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(1024)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(512)`); + } +} diff --git a/packages/backend/migration/1752502434151-no-action-on-draft-relation.js b/packages/backend/migration/1752502434151-no-action-on-draft-relation.js new file mode 100644 index 0000000000..e3c63b79c7 --- /dev/null +++ b/packages/backend/migration/1752502434151-no-action-on-draft-relation.js @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NoActionOnDraftRelation1752502434151 { + name = 'NoActionOnDraftRelation1752502434151' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_USER_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_e4983f28b4b18b03491536052f5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_e4983f28b4b18b03491536052f5"`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_USER_ID" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1752509043847-migration-cleanup.js b/packages/backend/migration/1752509043847-migration-cleanup.js new file mode 100644 index 0000000000..450e22af0c --- /dev/null +++ b/packages/backend/migration/1752509043847-migration-cleanup.js @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MigrationCleanup1752509043847 { + name = 'MigrationCleanup1752509043847' + + async up(queryRunner) { + // 1745378064470-composite-note-index.js created a index ON "note" ("userId", "id" DESC) as IDX_724b311e6f883751f261ebe378 but should be named IDX_a6f649630f55af3888e5a42919 + await queryRunner.query(`ALTER INDEX "IDX_724b311e6f883751f261ebe378" RENAME TO "IDX_a6f649630f55af3888e5a42919"`); + + // 1713656541000-abuse-report-notification.js generated system_webhook with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()` + // see https://github.com/typeorm/typeorm/blob/f351757a15b9d2bd9d4222c69dcfd2316f46b5d1/src/driver/postgres/PostgresDriver.ts#L1575 + await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT now()`); + + // 1702718871541-ffVisibility.js defined a enum type "user_profile_followersVisibility_enum" but it should be "user_profile_followersvisibility_enum" (lowercase 'v') in typeorm + await queryRunner.query(`ALTER TYPE "public"."user_profile_followersVisibility_enum" RENAME TO "user_profile_followersvisibility_enum"`); + + // 1713656541000-abuse-report-notification.js generated abuse_report_notification_recipient with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()` + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT now()`); + + // 1690796169261-play-visibility.js added visibility column to flash table but it forgot to set NOT NULL constraint + await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`); + + // 1736686850345-createNoteDraft.js created note_draft with hand-written SQL but several types and comments are not correctly defined + await queryRunner.query(`CREATE TYPE "public"."note_draft_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE character varying(32) array`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE character varying(32) array`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE "public"."note_draft_visibility_enum" USING visibility::note_draft_visibility_enum`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS 'The ID of reply target.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS 'The ID of renote target.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS 'The ID of source channel.'`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET NOT NULL`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" DROP NOT NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE varchar[]`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE varchar[]`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE varchar`); + await queryRunner.query(`DROP TYPE "public"."note_draft_visibility_enum"`); + + await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`); + + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`); + + await queryRunner.query(`ALTER TYPE "public"."user_profile_followersvisibility_enum" RENAME TO "user_profile_followersVisibility_enum"`); + + await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`); + + await queryRunner.query(`ALTER INDEX "IDX_a6f649630f55af3888e5a42919" RENAME TO "IDX_724b311e6f883751f261ebe378"`); + } +} diff --git a/packages/backend/migration/1753863104203-remoteNotesCleaning.js b/packages/backend/migration/1753863104203-remoteNotesCleaning.js new file mode 100644 index 0000000000..8250d8d0df --- /dev/null +++ b/packages/backend/migration/1753863104203-remoteNotesCleaning.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoteNotesCleaning1753863104203 { + name = 'RemoteNotesCleaning1753863104203' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT false`); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\''); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\''); + } + + async down(queryRunner) { + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningExpiryDaysForEachNotes"'); + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningMaxProcessingDurationInMinutes"'); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableRemoteNotesCleaning"`); + } +} diff --git a/packages/backend/migration/1753868431598-remove_note_constraints.js b/packages/backend/migration/1753868431598-remove_note_constraints.js new file mode 100644 index 0000000000..29540cf9de --- /dev/null +++ b/packages/backend/migration/1753868431598-remove_note_constraints.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoveNoteConstraints1753868431598 { + name = 'RemoveNoteConstraints1753868431598' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_52ccc804d7c69037d558bac4c96"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js b/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js new file mode 100644 index 0000000000..1051b18e1b --- /dev/null +++ b/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class TweakDefaultFederationSettings1754019326356 { + name = 'TweakDefaultFederationSettings1754019326356' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'none'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'all'`); + } +} diff --git a/packages/backend/migration/1755168347001-PageCountInNote.js b/packages/backend/migration/1755168347001-PageCountInNote.js new file mode 100644 index 0000000000..9f1894ab2f --- /dev/null +++ b/packages/backend/migration/1755168347001-PageCountInNote.js @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PageCountInNote1755168347001 { + name = 'PageCountInNote1755168347001' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'`); + + // Update existing notes + // block_list CTE collects all page blocks on the pages including child blocks in the section blocks. + // The clipped_notes CTE counts how many distinct pages each note block is referenced in. + // Finally, we update the note table with the count of pages for each referenced note. + await queryRunner.query(` + WITH RECURSIVE block_list AS ( + ( + SELECT + page.id as page_id, + block as block + FROM page + CROSS JOIN LATERAL jsonb_array_elements(page.content) block + WHERE block->>'type' = 'note' OR block->>'type' = 'section' + ) + UNION ALL + ( + SELECT + block_list.page_id, + child_block AS block + FROM LATERAL ( + SELECT page_id, block + FROM block_list + WHERE block_list.block->>'type' = 'section' + ) block_list + CROSS JOIN LATERAL jsonb_array_elements(block_list.block->'children') child_block + WHERE child_block->>'type' = 'note' OR child_block->>'type' = 'section' + ) + ), + clipped_notes AS ( + SELECT + (block->>'note') AS note_id, + COUNT(distinct block_list.page_id) AS count + FROM block_list + WHERE block_list.block->>'type' = 'note' + GROUP BY block->>'note' + ) + UPDATE note + SET "pageCount" = clipped_notes.count + FROM clipped_notes + WHERE note.id = clipped_notes.note_id; + `); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "pageCount"`); + } +} diff --git a/packages/backend/migration/1755574887486-entrancePageStyle.js b/packages/backend/migration/1755574887486-entrancePageStyle.js new file mode 100644 index 0000000000..ba40764b94 --- /dev/null +++ b/packages/backend/migration/1755574887486-entrancePageStyle.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class EntrancePageStyle1755574887486 { + name = 'EntrancePageStyle1755574887486' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "clientOptions" jsonb NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "clientOptions"`); + } +} diff --git a/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js new file mode 100644 index 0000000000..8554cc4304 --- /dev/null +++ b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NonCascadingPageEyeCatching1756062689648 { + name = 'NonCascadingPageEyeCatching1756062689648' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1757823175259-sensitive-ad.js b/packages/backend/migration/1757823175259-sensitive-ad.js new file mode 100644 index 0000000000..46f0f270ab --- /dev/null +++ b/packages/backend/migration/1757823175259-sensitive-ad.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SensitiveAd1757823175259 { + name = 'SensitiveAd1757823175259' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" ADD "isSensitive" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "isSensitive"`); + } +} diff --git a/packages/backend/migration/1758677617888-scheduled-post.js b/packages/backend/migration/1758677617888-scheduled-post.js new file mode 100644 index 0000000000..b31313d9db --- /dev/null +++ b/packages/backend/migration/1758677617888-scheduled-post.js @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ScheduledPost1758677617888 { + name = 'ScheduledPost1758677617888' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" ADD "scheduledAt" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD "isActuallyScheduled" boolean NOT NULL DEFAULT false`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP COLUMN "isActuallyScheduled"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP COLUMN "scheduledAt"`); + } +} diff --git a/packages/backend/migration/1760607435831-RoleBadgesRemoteUsers.js b/packages/backend/migration/1760607435831-RoleBadgesRemoteUsers.js new file mode 100644 index 0000000000..483d35a91b --- /dev/null +++ b/packages/backend/migration/1760607435831-RoleBadgesRemoteUsers.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RoleBadgesRemoteUsers1760607435831 { + name = 'RoleBadgesRemoteUsers1760607435831' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "showRoleBadgesOfRemoteUsers" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "showRoleBadgesOfRemoteUsers"`); + } +} diff --git a/packages/backend/migration/1760790899857-unnecessary-null-default.js b/packages/backend/migration/1760790899857-unnecessary-null-default.js new file mode 100644 index 0000000000..d34758315f --- /dev/null +++ b/packages/backend/migration/1760790899857-unnecessary-null-default.js @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UnnecessaryNullDefault1760790899857 { + name = 'UnnecessaryNullDefault1760790899857' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "userId" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "systemWebhookId" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "urlPreviewUserAgent" DROP DEFAULT`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "urlPreviewUserAgent" SET DEFAULT NULL`); + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "systemWebhookId" SET DEFAULT NULL`); + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "userId" SET DEFAULT NULL`); + } +} 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/migration/1767169026317-birthday-index.js b/packages/backend/migration/1767169026317-birthday-index.js new file mode 100644 index 0000000000..972fc08c9b --- /dev/null +++ b/packages/backend/migration/1767169026317-birthday-index.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class BirthdayIndex1767169026317 { + name = 'BirthdayIndex1767169026317' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_de22cd2b445eee31ae51cdbe99"`); + await queryRunner.query(`CREATE OR REPLACE FUNCTION get_birthday_date(birthday TEXT) RETURNS SMALLINT AS $$ BEGIN RETURN CAST((SUBSTR(birthday, 6, 2) || SUBSTR(birthday, 9, 2)) AS SMALLINT); END; $$ LANGUAGE plpgsql IMMUTABLE;`); + await queryRunner.query(`CREATE INDEX "IDX_USERPROFILE_BIRTHDAY_DATE" ON "user_profile" (get_birthday_date("birthday"))`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_de22cd2b445eee31ae51cdbe99" ON "user_profile" (substr("birthday", 6, 5))`); + await queryRunner.query(`DROP INDEX "public"."IDX_USERPROFILE_BIRTHDAY_DATE"`); + await queryRunner.query(`DROP FUNCTION IF EXISTS get_birthday_date(birthday TEXT)`); + } +} diff --git a/packages/backend/ormconfig.js b/packages/backend/ormconfig.js index 229e5bf1fe..1a8c146451 100644 --- a/packages/backend/ormconfig.js +++ b/packages/backend/ormconfig.js @@ -1,6 +1,8 @@ import { DataSource } from 'typeorm'; -import { loadConfig } from './built/config.js'; -import { entities } from './built/postgres.js'; +import { loadConfig } from './src-js/config.js'; +import { entities } from './src-js/postgres.js'; + +const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1'; const config = loadConfig(); @@ -14,4 +16,5 @@ export default new DataSource({ extra: config.db.extra, entities: entities, migrations: ['migration/*.js'], + migrationsTransactionMode: isConcurrentIndexMigrationEnabled ? 'each' : 'all', }); diff --git a/packages/backend/package.json b/packages/backend/package.json index 21d02a7045..77016ce209 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,53 +4,57 @@ "private": true, "type": "module", "engines": { - "node": "^20.10.0 || ^22.0.0" + "node": "^22.15.0 || ^24.10.0" }, "scripts": { - "start": "node ./built/boot/entry.js", - "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js", - "migrate": "pnpm typeorm migration:run -d ormconfig.js", - "revert": "pnpm typeorm migration:revert -d ormconfig.js", - "check:connect": "node ./scripts/check_connect.js", - "build": "swc src -d built -D --strip-leading-paths", + "start": "pnpm compile-config && node ./built/boot/entry.js", + "start:inspect": "pnpm compile-config && node --inspect ./built/boot/entry.js", + "start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js", + "migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js", + "revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js", + "cli": "pnpm compile-config && node ./src-js/boot/cli.js", + "check:connect": "pnpm compile-config && node ./scripts/check_connect.js", + "compile-config": "node ./scripts/compile_config.js", + "build": "swc src -d src-js -D --strip-leading-paths && node ./build.js", "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths", "watch:swc": "swc src -d built -D -w --strip-leading-paths", - "build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json", - "watch": "node ./scripts/watch.mjs", + "build:tsc": "tsgo -p tsconfig.json && tsc-alias -p tsconfig.json", + "watch": "pnpm compile-config && node ./scripts/watch.mjs", "restart": "pnpm build && pnpm start", - "dev": "node ./scripts/dev.mjs", - "typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit", + "dev": "pnpm compile-config && node ./scripts/dev.mjs", + "typecheck": "tsgo --noEmit && tsgo -p test --noEmit && tsgo -p test-federation --noEmit", "eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"", "lint": "pnpm typecheck && pnpm eslint", - "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs", - "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs", - "jest:fed": "node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.fed.cjs", - "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs", - "jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs", - "jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache", + "jest": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs", + "jest:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs", + "jest:fed": "pnpm compile-config && node ./jest.js --forceExit --config jest.config.fed.cjs", + "jest-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs", + "jest-and-coverage:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs", + "jest-clear": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --clearCache", "test": "pnpm jest", "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", "test:fed": "pnpm jest:fed", "test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", - "generate-api-json": "node ./scripts/generate_api_json.js" + "check-migrations": "node scripts/check_migrations_clean.js", + "generate-api-json": "pnpm compile-config && node ./scripts/generate_api_json.js" }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.11.18", - "@swc/core-darwin-x64": "1.11.18", + "@swc/core-darwin-arm64": "1.15.7", + "@swc/core-darwin-x64": "1.15.7", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.11.18", - "@swc/core-linux-arm64-gnu": "1.11.18", - "@swc/core-linux-arm64-musl": "1.11.18", - "@swc/core-linux-x64-gnu": "1.11.18", - "@swc/core-linux-x64-musl": "1.11.18", - "@swc/core-win32-arm64-msvc": "1.11.18", - "@swc/core-win32-ia32-msvc": "1.11.18", - "@swc/core-win32-x64-msvc": "1.11.18", + "@swc/core-linux-arm-gnueabihf": "1.15.7", + "@swc/core-linux-arm64-gnu": "1.15.7", + "@swc/core-linux-arm64-musl": "1.15.7", + "@swc/core-linux-x64-gnu": "1.15.7", + "@swc/core-linux-x64-musl": "1.15.7", + "@swc/core-win32-arm64-msvc": "1.15.7", + "@swc/core-win32-ia32-msvc": "1.15.7", + "@swc/core-win32-x64-msvc": "1.15.7", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", - "bufferutil": "4.0.9", + "bufferutil": "4.1.0", "slacc-android-arm-eabi": "0.0.10", "slacc-android-arm64": "0.0.10", "slacc-darwin-arm64": "0.0.10", @@ -64,181 +68,167 @@ "slacc-linux-x64-musl": "0.0.10", "slacc-win32-arm64-msvc": "0.0.10", "slacc-win32-x64-msvc": "0.0.10", - "utf-8-validate": "6.0.5" + "utf-8-validate": "6.0.6" }, "dependencies": { - "@aws-sdk/client-s3": "3.782.0", - "@aws-sdk/lib-storage": "3.782.0", - "@discordapp/twemoji": "15.1.0", - "@fastify/accepts": "5.0.2", - "@fastify/cookie": "11.0.2", - "@fastify/cors": "10.1.0", + "@aws-sdk/client-s3": "3.958.0", + "@aws-sdk/lib-storage": "3.958.0", + "@discordapp/twemoji": "16.0.1", + "@fastify/accepts": "5.0.4", + "@fastify/cors": "11.2.0", "@fastify/express": "4.0.2", - "@fastify/http-proxy": "10.0.2", - "@fastify/multipart": "9.0.3", - "@fastify/static": "8.1.1", - "@fastify/view": "10.0.2", - "@misskey-dev/sharp-read-bmp": "1.3.0", - "@misskey-dev/summaly": "5.2.0", - "@napi-rs/canvas": "0.1.69", - "@nestjs/common": "11.0.16", - "@nestjs/core": "11.0.15", - "@nestjs/testing": "11.0.15", + "@fastify/http-proxy": "11.4.1", + "@fastify/multipart": "9.3.0", + "@fastify/static": "8.3.0", + "@kitajs/html": "4.2.11", + "@misskey-dev/sharp-read-bmp": "1.2.0", + "@misskey-dev/summaly": "5.2.5", + "@napi-rs/canvas": "0.1.87", + "@nestjs/common": "11.1.10", + "@nestjs/core": "11.1.10", + "@nestjs/testing": "11.1.10", "@peertube/http-signature": "1.7.0", - "@sentry/node": "8.55.0", - "@sentry/profiling-node": "8.55.0", - "@simplewebauthn/server": "12.0.0", - "@sinonjs/fake-timers": "11.3.1", - "@smithy/node-http-handler": "2.5.0", - "@swc/cli": "0.6.0", - "@swc/core": "1.11.18", - "@twemoji/parser": "15.1.1", - "@types/redis-info": "3.0.3", + "@sentry/node": "10.32.1", + "@sentry/profiling-node": "10.32.1", + "@simplewebauthn/server": "13.2.2", + "@sinonjs/fake-timers": "15.1.0", + "@smithy/node-http-handler": "4.4.7", + "@swc/cli": "0.7.9", + "@swc/core": "1.15.7", + "@twemoji/parser": "16.0.0", "accepts": "1.3.8", "ajv": "8.17.1", "archiver": "7.0.1", "async-mutex": "0.5.0", - "bcryptjs": "2.4.3", + "bcryptjs": "3.0.3", "blurhash": "2.0.5", - "body-parser": "1.20.3", - "bullmq": "5.48.1", + "body-parser": "2.2.1", + "bullmq": "5.66.3", "cacheable-lookup": "7.0.0", - "cbor": "9.0.2", - "chalk": "5.4.1", - "chalk-template": "1.1.0", - "chokidar": "3.6.0", - "cli-highlight": "2.1.11", - "color-convert": "2.0.1", - "content-disposition": "0.5.4", - "date-fns": "2.30.0", + "chalk": "5.6.2", + "chalk-template": "1.1.2", + "chokidar": "5.0.0", + "color-convert": "3.1.3", + "content-disposition": "1.0.1", + "date-fns": "4.1.0", "deep-email-validator": "0.1.21", - "fastify": "5.2.2", + "fastify": "5.6.2", "fastify-raw-body": "5.0.0", - "feed": "4.2.2", - "file-type": "19.6.0", + "feed": "5.1.0", + "file-type": "21.2.0", "fluent-ffmpeg": "2.1.3", - "form-data": "4.0.2", - "got": "14.4.7", - "happy-dom": "16.8.1", + "form-data": "4.0.5", + "got": "14.6.5", "hpagent": "1.2.0", - "htmlescape": "1.1.1", "http-link-header": "1.1.3", - "ioredis": "5.6.0", + "i18n": "workspace:*", + "ioredis": "5.8.2", "ip-cidr": "4.0.2", - "ipaddr.js": "2.2.0", - "is-svg": "5.1.0", - "js-yaml": "4.1.0", - "jsdom": "26.0.0", + "ipaddr.js": "2.3.0", + "is-svg": "6.1.0", "json5": "2.2.3", - "jsonld": "8.3.3", - "jsrsasign": "11.1.0", - "juice": "11.0.1", - "meilisearch": "0.49.0", - "mfm-js": "0.24.0", - "microformats-parser": "2.0.2", - "mime-types": "2.1.35", + "jsonld": "9.0.0", + "juice": "11.0.3", + "meilisearch": "0.54.0", + "mfm-js": "0.25.0", + "mime-types": "3.0.2", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", "misskey-mahjong": "workspace:*", - "ms": "3.0.0-canary.1", - "nanoid": "5.1.5", + "ms": "3.0.0-canary.202508261828", + "nanoid": "5.1.6", "nested-property": "4.0.0", "node-fetch": "3.3.2", - "nodemailer": "6.10.0", + "node-html-parser": "7.0.1", + "nodemailer": "7.0.12", "nsfwjs": "4.2.0", - "oauth": "0.10.2", "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", - "otpauth": "9.4.0", - "parse5": "7.2.1", - "pg": "8.14.1", - "pkce-challenge": "4.1.0", + "otpauth": "9.4.1", + "pg": "8.16.3", + "pkce-challenge": "5.0.1", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", - "pug": "3.0.3", "qrcode": "1.5.4", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.21.4", - "redis-info": "3.1.0", - "redis-lock": "0.1.4", + "re2": "1.23.0", "reflect-metadata": "0.2.2", "rename": "1.0.4", "rss-parser": "3.13.0", "rxjs": "7.8.2", - "sanitize-html": "2.15.0", - "secure-json-parse": "3.0.2", - "sharp": "0.34.1", + "sanitize-html": "2.17.0", + "secure-json-parse": "4.1.0", + "semver": "7.7.3", + "sharp": "0.33.5", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.25.11", + "systeminformation": "5.28.1", "tinycolor2": "1.6.0", - "tmp": "0.2.3", - "tsc-alias": "1.8.15", - "tsconfig-paths": "4.2.0", - "typeorm": "0.3.22", - "typescript": "5.8.3", - "ulid": "2.4.0", + "tmp": "0.2.5", + "tsc-alias": "1.8.16", + "typeorm": "0.3.28", + "ulid": "3.0.2", "vary": "1.1.2", "web-push": "3.6.7", - "ws": "8.18.1", + "ws": "8.18.3", "xev": "3.0.2" }, "devDependencies": { "@jest/globals": "29.7.0", - "@nestjs/platform-express": "10.4.15", - "@sentry/vue": "9.12.0", + "@kitajs/ts-html-plugin": "4.1.3", + "@nestjs/platform-express": "11.1.10", + "@sentry/vue": "10.32.1", "@simplewebauthn/types": "12.0.0", - "@swc/jest": "0.2.37", + "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", - "@types/archiver": "6.0.3", - "@types/bcryptjs": "2.4.6", - "@types/body-parser": "1.19.5", + "@types/archiver": "7.0.0", + "@types/body-parser": "1.19.6", "@types/color-convert": "2.0.4", - "@types/content-disposition": "0.5.8", - "@types/fluent-ffmpeg": "2.1.27", - "@types/htmlescape": "1.1.3", + "@types/content-disposition": "0.5.9", + "@types/fluent-ffmpeg": "2.1.28", "@types/http-link-header": "1.0.7", "@types/jest": "29.5.14", - "@types/js-yaml": "4.0.9", - "@types/jsdom": "21.1.7", "@types/jsonld": "1.5.15", - "@types/jsrsasign": "10.5.15", - "@types/mime-types": "2.1.4", - "@types/ms": "0.7.34", - "@types/node": "22.14.0", - "@types/nodemailer": "6.4.17", - "@types/oauth": "0.9.6", + "@types/mime-types": "3.0.1", + "@types/ms": "2.1.0", + "@types/node": "24.10.4", + "@types/nodemailer": "7.0.4", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.11.11", - "@types/pug": "2.0.10", - "@types/qrcode": "1.5.5", + "@types/pg": "8.16.0", + "@types/qrcode": "1.5.6", "@types/random-seed": "0.3.5", "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", - "@types/sanitize-html": "2.15.0", - "@types/semver": "7.7.0", - "@types/simple-oauth2": "5.0.7", - "@types/sinonjs__fake-timers": "8.1.5", + "@types/sanitize-html": "2.16.0", + "@types/semver": "7.7.1", + "@types/simple-oauth2": "5.0.8", + "@types/sinonjs__fake-timers": "15.0.1", + "@types/supertest": "6.0.3", "@types/tinycolor2": "1.4.6", "@types/tmp": "0.2.6", "@types/vary": "1.1.3", "@types/web-push": "3.6.4", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", "aws-sdk-client-mock": "4.1.0", - "cross-env": "7.0.3", - "eslint-plugin-import": "2.31.0", - "execa": "8.0.1", - "fkill": "9.0.0", + "cbor": "10.0.11", + "cross-env": "10.1.0", + "esbuild-plugin-swc": "1.0.1", + "eslint-plugin-import": "2.32.0", + "execa": "9.6.1", + "fkill": "10.0.1", "jest": "29.7.0", "jest-mock": "29.7.0", - "nodemon": "3.1.9", - "pid-port": "1.0.2", - "simple-oauth2": "5.1.0" + "js-yaml": "4.1.1", + "nodemon": "3.1.11", + "pid-port": "2.0.0", + "simple-oauth2": "5.1.0", + "supertest": "7.1.4", + "vite": "7.3.0" } } diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js index 96c4549ccb..a1cb839303 100644 --- a/packages/backend/scripts/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -4,8 +4,8 @@ */ import Redis from 'ioredis'; -import { loadConfig } from '../built/config.js'; -import { createPostgresDataSource } from '../built/postgres.js'; +import { loadConfig } from '../src-js/config.js'; +import { createPostgresDataSource } from '../src-js/postgres.js'; const config = loadConfig(); @@ -16,26 +16,22 @@ async function connectToPostgres() { } async function connectToRedis(redisOptions) { - return await new Promise(async (resolve, reject) => { - const redis = new Redis({ + let redis; + try { + redis = new Redis({ ...redisOptions, lazyConnect: true, reconnectOnError: false, showFriendlyErrorStack: true, }); - redis.on('error', e => reject(e)); - try { - await redis.connect(); - resolve(); - - } catch (e) { - reject(e); - - } finally { - redis.disconnect(false); - } - }); + await Promise.race([ + new Promise((_, reject) => redis.on('error', e => reject(e))), + redis.connect(), + ]); + } finally { + redis.disconnect(false); + } } // If not all of these are defined, the default one gets reused. @@ -50,7 +46,7 @@ const promises = Array ])) .map(connectToRedis) .concat([ - connectToPostgres() + connectToPostgres(), ]); await Promise.all(promises); diff --git a/packages/backend/scripts/check_migrations_clean.js b/packages/backend/scripts/check_migrations_clean.js new file mode 100644 index 0000000000..ce67b1cd81 --- /dev/null +++ b/packages/backend/scripts/check_migrations_clean.js @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// This script checks if the database migrations has been generated correctly. + +import dataSource from '../ormconfig.js'; + +await dataSource.initialize(); + +const sqlInMemory = await dataSource.driver.createSchemaBuilder().log(); + +if (sqlInMemory.upQueries.length > 0 || sqlInMemory.downQueries.length > 0) { + console.error('There are several pending migrations. Please make sure you have generated the migrations correctly, or configured entities class correctly.'); + for (const query of sqlInMemory.upQueries) { + console.error(`- ${query.query}`); + } + for (const query of sqlInMemory.downQueries) { + console.error(`- ${query.query}`); + } + process.exit(1); +} else { + console.log('All migrations are clean.'); + process.exit(0); +} diff --git a/packages/backend/scripts/compile_config.js b/packages/backend/scripts/compile_config.js new file mode 100644 index 0000000000..e78fa3dc9f --- /dev/null +++ b/packages/backend/scripts/compile_config.js @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * YAMLファイルをJSONファイルに変換するスクリプト + * ビルド前に実行し、ランタイムにjs-yamlを含まないようにする + */ + +import fs from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import yaml from 'js-yaml'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const configDir = resolve(_dirname, '../../../.config'); +const OUTPUT_PATH = resolve(_dirname, '../../../built/.config.json'); + +// TODO: yamlのパースに失敗したときのエラーハンドリング + +/** + * YAMLファイルをJSONファイルに変換 + * @param {string} ymlPath - YAMLファイルのパス + */ +function yamlToJson(ymlPath) { + if (!fs.existsSync(ymlPath)) { + console.warn(`YAML file not found: ${ymlPath}`); + return; + } + + console.log(`${ymlPath} → ${OUTPUT_PATH}`); + + const yamlContent = fs.readFileSync(ymlPath, 'utf-8'); + const jsonContent = yaml.load(yamlContent); + if (!fs.existsSync(dirname(OUTPUT_PATH))) { + fs.mkdirSync(dirname(OUTPUT_PATH), { recursive: true }); + } + fs.writeFileSync(OUTPUT_PATH, JSON.stringify({ + '_NOTE_': 'This file is auto-generated from YAML file. DO NOT EDIT.', + ...jsonContent, + }), 'utf-8'); +} + +if (process.env.MISSKEY_CONFIG_YML) { + const customYmlPath = resolve(configDir, process.env.MISSKEY_CONFIG_YML); + yamlToJson(customYmlPath); +} else { + yamlToJson(resolve(configDir, process.env.NODE_ENV === 'test' ? 'test.yml' : 'default.yml')); +} + +console.log('Configuration compiled ✓'); diff --git a/packages/backend/scripts/dev.mjs b/packages/backend/scripts/dev.mjs index a3e0558abd..db96eaf976 100644 --- a/packages/backend/scripts/dev.mjs +++ b/packages/backend/scripts/dev.mjs @@ -42,7 +42,7 @@ async function killProc() { './node_modules/nodemon/bin/nodemon.js', [ '-w', 'src', - '-e', 'ts,js,mjs,cjs,json', + '-e', 'ts,js,mjs,cjs,tsx,json,pug', '--exec', 'pnpm', 'run', 'build', ], { diff --git a/packages/backend/scripts/generate_api_json.js b/packages/backend/scripts/generate_api_json.js index 798e243004..237f63a4d3 100644 --- a/packages/backend/scripts/generate_api_json.js +++ b/packages/backend/scripts/generate_api_json.js @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { writeFileSync, existsSync } from 'node:fs'; import { execa } from 'execa'; -import { writeFileSync, existsSync } from "node:fs"; async function main() { if (!process.argv.includes('--no-build')) { @@ -19,10 +19,10 @@ async function main() { } /** @type {import('../src/config.js')} */ - const { loadConfig } = await import('../built/config.js'); + const { loadConfig } = await import('../src-js/config.js'); /** @type {import('../src/server/api/openapi/gen-spec.js')} */ - const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js'); + const { genOpenapiSpec } = await import('../src-js/server/api/openapi/gen-spec.js'); const config = loadConfig(); const spec = genOpenapiSpec(config, true); diff --git a/packages/backend/scripts/measure-memory.mjs b/packages/backend/scripts/measure-memory.mjs new file mode 100644 index 0000000000..baa4198adf --- /dev/null +++ b/packages/backend/scripts/measure-memory.mjs @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * This script starts the Misskey backend server, waits for it to be ready, + * measures memory usage, and outputs the result as JSON. + * + * Usage: node scripts/measure-memory.mjs + */ + +import { fork } from 'node:child_process'; +import { setTimeout } from 'node:timers/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup +const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle + +async function measureMemory() { + const startTime = Date.now(); + + // Start the Misskey backend server using fork to enable IPC + const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], { + cwd: join(__dirname, '..'), + env: { + ...process.env, + NODE_ENV: 'production', + MK_DISABLE_CLUSTERING: '1', + MK_FORCE_GC: '1', + }, + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + }); + + let serverReady = false; + + // Listen for the 'ok' message from the server indicating it's ready + serverProcess.on('message', (message) => { + if (message === 'ok') { + serverReady = true; + } + }); + + // Handle server output + serverProcess.stdout?.on('data', (data) => { + process.stderr.write(`[server stdout] ${data}`); + }); + + serverProcess.stderr?.on('data', (data) => { + process.stderr.write(`[server stderr] ${data}`); + }); + + // Handle server error + serverProcess.on('error', (err) => { + process.stderr.write(`[server error] ${err}\n`); + }); + + // Wait for server to be ready or timeout + const startupStartTime = Date.now(); + while (!serverReady) { + if (Date.now() - startupStartTime > STARTUP_TIMEOUT) { + serverProcess.kill('SIGTERM'); + throw new Error('Server startup timeout'); + } + await setTimeout(100); + } + + const startupTime = Date.now() - startupStartTime; + process.stderr.write(`Server started in ${startupTime}ms\n`); + + // Wait for memory to settle + await setTimeout(MEMORY_SETTLE_TIME); + + // Get memory usage from the server process via /proc + const pid = serverProcess.pid; + let memoryInfo; + + try { + const fs = await import('node:fs/promises'); + + // Read /proc/[pid]/status for detailed memory info + const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8'); + const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/); + const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/); + const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/); + + memoryInfo = { + rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null, + heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null, + vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null, + }; + } catch (err) { + // Fallback: use ps command + process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`); + + const { execSync } = await import('node:child_process'); + try { + const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' }); + const rssKb = parseInt(ps.trim(), 10); + memoryInfo = { + rss: rssKb * 1024, + heapUsed: null, + vmSize: null, + }; + } catch { + memoryInfo = { + rss: null, + heapUsed: null, + vmSize: null, + error: 'Could not measure memory', + }; + } + } + + // Stop the server + serverProcess.kill('SIGTERM'); + + // Wait for process to exit + let exited = false; + await new Promise((resolve) => { + serverProcess.on('exit', () => { + exited = true; + resolve(undefined); + }); + // Force kill after 10 seconds if not exited + setTimeout(10000).then(() => { + if (!exited) { + serverProcess.kill('SIGKILL'); + } + resolve(undefined); + }); + }); + + const result = { + timestamp: new Date().toISOString(), + startupTimeMs: startupTime, + memory: memoryInfo, + }; + + // Output as JSON to stdout + console.log(JSON.stringify(result, null, 2)); +} + +measureMemory().catch((err) => { + console.error(JSON.stringify({ + error: err.message, + timestamp: new Date().toISOString(), + })); + process.exit(1); +}); diff --git a/packages/backend/scripts/watch.mjs b/packages/backend/scripts/watch.mjs index a0ccea3b16..9d608b233c 100644 --- a/packages/backend/scripts/watch.mjs +++ b/packages/backend/scripts/watch.mjs @@ -21,7 +21,7 @@ import { execa } from 'execa'; }); }, 3000); - execa('tsc', ['-w', '-p', 'tsconfig.json'], { + execa('tsgo', ['-w', '-p', 'tsconfig.json'], { stdout: process.stdout, stderr: process.stderr, }); diff --git a/packages/backend/src/@types/redis-lock.d.ts b/packages/backend/src/@types/redis-lock.d.ts deleted file mode 100644 index b037cde5ee..0000000000 --- a/packages/backend/src/@types/redis-lock.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -declare module 'redis-lock' { - import type Redis from 'ioredis'; - - type Lock = (lockName: string, timeout?: number, taskToPerform?: () => Promise) => void; - function redisLock(client: Redis.Redis, retryDelay: number): Lock; - - export = redisLock; -} diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 5544eeeddd..435bd8dd45 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -24,8 +24,13 @@ const $config: Provider = { const $db: Provider = { provide: DI.db, useFactory: async (config) => { - const db = createPostgresDataSource(config); - return await db.initialize(); + try { + const db = createPostgresDataSource(config); + return await db.initialize(); + } catch (e) { + console.log(e); + throw e; + } }, inject: [DI.config], }; diff --git a/packages/backend/src/boot/cli.ts b/packages/backend/src/boot/cli.ts new file mode 100644 index 0000000000..a5618f8152 --- /dev/null +++ b/packages/backend/src/boot/cli.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import 'reflect-metadata'; +import { EventEmitter } from 'node:events'; +import { NestFactory } from '@nestjs/core'; +import { CommandModule } from '@/cli/CommandModule.js'; +import { NestLogger } from '@/NestLogger.js'; +import { CommandService } from '@/cli/CommandService.js'; + +process.title = 'Misskey Cli'; + +Error.stackTraceLimit = Infinity; +EventEmitter.defaultMaxListeners = 128; + +const app = await NestFactory.createApplicationContext(CommandModule, { + logger: new NestLogger(), +}); + +const commandService = app.get(CommandService); + +const command = process.argv[2] ?? 'help'; + +switch (command) { + case 'help': { + console.log('Available commands:'); + console.log(' help - Displays this help message'); + console.log(' reset-captcha - Resets the captcha'); + break; + } + case 'ping': { + await commandService.ping(); + break; + } + case 'reset-captcha': { + await commandService.resetCaptcha(); + console.log('Captcha has been reset.'); + break; + } + default: { + console.error(`Unrecognized command: ${command}`); + console.error('Use "help" to see available commands.'); + process.exit(1); + } +} + +process.exit(0); diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index da585ad68d..56b339b6aa 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -86,6 +86,10 @@ if (!envOption.disableClustering) { ev.mount(); } +if (envOption.forceGc && global.gc != null) { + global.gc(); +} + readyRef.value = true; // ユニットテスト時にMisskeyが子プロセスで起動された時のため diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index d1fb3858db..041f58e509 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -4,14 +4,10 @@ */ import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; import * as os from 'node:os'; import cluster from 'node:cluster'; import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; -import * as Sentry from '@sentry/node'; -import { nodeProfilingIntegration } from '@sentry/profiling-node'; import Logger from '@/logger.js'; import { loadConfig } from '@/config.js'; import type { Config } from '@/config.js'; @@ -19,20 +15,15 @@ import { showMachineInfo } from '@/misc/show-machine-info.js'; import { envOption } from '@/env.js'; import { jobQueue, server } from './common.js'; -const _filename = fileURLToPath(import.meta.url); -const _dirname = dirname(_filename); - -const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); - const logger = new Logger('core', 'cyan'); const bootLogger = logger.createSubLogger('boot', 'magenta'); const themeColor = chalk.hex('#86b300'); -function greet() { +function greet(props: { version: string }) { if (!envOption.quiet) { //#region Misskey logo - const v = `v${meta.version}`; + const v = `v${props.version}`; console.log(themeColor(' _____ _ _ ')); console.log(themeColor(' | |_|___ ___| |_ ___ _ _ ')); console.log(themeColor(' | | | | |_ -|_ -| \'_| -_| | |')); @@ -41,14 +32,14 @@ function greet() { //#endregion console.log(' Misskey is an open-source decentralized microblogging platform.'); - console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); + console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please consider donating to support dev. https://misskey-hub.net/docs/donate/')); console.log(''); console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`); } bootLogger.info('Welcome to Misskey!'); - bootLogger.info(`Misskey v${meta.version}`, null, true); + bootLogger.info(`Misskey v${props.version}`, null, true); } /** @@ -59,21 +50,24 @@ export async function masterMain() { // initialize app try { - greet(); + config = loadConfigBoot(); + greet({ version: config.version }); showEnvironment(); await showMachineInfo(bootLogger); showNodejsVersion(); - config = loadConfigBoot(); //await connectDb(); if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString()); } catch (e) { - bootLogger.error('Fatal error occurred during initialization', null, true); + bootLogger.error('Fatal error occurred during initialization: ' + e, null, true); process.exit(1); } bootLogger.succ('Misskey initialized'); if (config.sentryForBackend) { + const Sentry = await import('@sentry/node'); + const { nodeProfilingIntegration } = await import('@sentry/profiling-node'); + Sentry.init({ integrations: [ ...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []), diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts index 5d4a15b29f..3feb6fd199 100644 --- a/packages/backend/src/boot/worker.ts +++ b/packages/backend/src/boot/worker.ts @@ -4,8 +4,6 @@ */ import cluster from 'node:cluster'; -import * as Sentry from '@sentry/node'; -import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { envOption } from '@/env.js'; import { loadConfig } from '@/config.js'; import { jobQueue, server } from './common.js'; @@ -17,6 +15,9 @@ export async function workerMain() { const config = loadConfig(); if (config.sentryForBackend) { + const Sentry = await import('@sentry/node'); + const { nodeProfilingIntegration } = await import('@sentry/profiling-node'); + Sentry.init({ integrations: [ ...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []), diff --git a/packages/backend/src/cli/CommandModule.ts b/packages/backend/src/cli/CommandModule.ts new file mode 100644 index 0000000000..f4b1d25c18 --- /dev/null +++ b/packages/backend/src/cli/CommandModule.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Module } from '@nestjs/common'; +import { CoreModule } from '@/core/CoreModule.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CommandService } from './CommandService.js'; + +@Module({ + imports: [ + GlobalModule, + CoreModule, + ], + providers: [ + CommandService, + ], + exports: [ + CommandService, + ], +}) +export class CommandModule {} diff --git a/packages/backend/src/cli/CommandService.ts b/packages/backend/src/cli/CommandService.ts new file mode 100644 index 0000000000..cdb2a9f382 --- /dev/null +++ b/packages/backend/src/cli/CommandService.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; + +@Injectable() +export class CommandService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + private metaService: MetaService, + ) { + } + + @bindThis + public async ping() { + console.log('pong'); + } + + @bindThis + public async resetCaptcha() { + await this.metaService.update({ + enableHcaptcha: false, + hcaptchaSiteKey: null, + hcaptchaSecretKey: null, + enableMcaptcha: false, + mcaptchaSitekey: null, + mcaptchaSecretKey: null, + mcaptchaInstanceUrl: null, + enableRecaptcha: false, + recaptchaSiteKey: null, + recaptchaSecretKey: null, + enableTurnstile: false, + turnstileSiteKey: null, + turnstileSecretKey: null, + enableTestcaptcha: false, + }); + } +} diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 646fa07911..4cd82bed87 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -6,10 +6,11 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; -import * as yaml from 'js-yaml'; +import { type FastifyServerOptions } from 'fastify'; import type * as Sentry from '@sentry/node'; import type * as SentryVue from '@sentry/vue'; import type { RedisOptions } from 'ioredis'; +import type { ManifestChunk } from 'vite'; type RedisOptionsSource = Partial & { host: string; @@ -27,7 +28,9 @@ type Source = { url?: string; port?: number; socket?: string; + trustProxy?: FastifyServerOptions['trustProxy']; chmodSocket?: string; + enableIpRateLimit?: boolean; disableHsts?: boolean; db: { host: string; @@ -79,7 +82,6 @@ type Source = { proxyBypassHosts?: string[]; allowedPrivateNetworks?: string[]; - disallowExternalApRedirect?: boolean; maxFileSize?: number; @@ -100,11 +102,8 @@ type Source = { inboxJobMaxAttempts?: number; mediaProxy?: string; - proxyRemoteFiles?: boolean; videoThumbnailGenerator?: string; - signToActivityPubGet?: boolean; - perChannelMaxNoteCacheCount?: number; perUserNotificationsMaxCount?: number; deactivateAntennaThreshold?: number; @@ -122,7 +121,9 @@ export type Config = { url: string; port: number; socket: string | undefined; + trustProxy: NonNullable; chmodSocket: string | undefined; + enableIpRateLimit: boolean; disableHsts: boolean | undefined; db: { host: string; @@ -156,7 +157,6 @@ export type Config = { proxySmtp: string | undefined; proxyBypassHosts: string[] | undefined; allowedPrivateNetworks: string[] | undefined; - disallowExternalApRedirect: boolean; maxFileSize: number; clusterLimit: number | undefined; id: string; @@ -170,8 +170,6 @@ export type Config = { relationshipJobPerSec: number | undefined; deliverJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined; - proxyRemoteFiles: boolean | undefined; - signToActivityPubGet: boolean | undefined; logging?: { sql?: { disableQueryTruncation?: boolean, @@ -191,9 +189,9 @@ export type Config = { authUrl: string; driveUrl: string; userAgent: string; - frontendEntry: string; + frontendEntry: ManifestChunk; frontendManifestExists: boolean; - frontendEmbedEntry: string; + frontendEmbedEntry: ManifestChunk; frontendEmbedManifestExists: boolean; mediaProxy: string; externalMediaProxyEnabled: boolean; @@ -221,33 +219,45 @@ export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -/** - * Path of configuration directory - */ -const dir = `${_dirname}/../../../.config`; +/** Path of repository root directory */ +let rootDir = _dirname; +// 見つかるまで上に遡る +while (!fs.existsSync(resolve(rootDir, 'packages'))) { + const parentDir = dirname(rootDir); + if (parentDir === rootDir) { + throw new Error('Cannot find root directory'); + } + rootDir = parentDir; +} -/** - * Path of configuration file - */ -const path = process.env.MISSKEY_CONFIG_YML - ? resolve(dir, process.env.MISSKEY_CONFIG_YML) - : process.env.NODE_ENV === 'test' - ? resolve(dir, 'test.yml') - : resolve(dir, 'default.yml'); +/** Path of configuration directory */ +const configDir = resolve(rootDir, '.config'); +/** Path of built directory */ +const projectBuiltDir = resolve(rootDir, 'built'); + +const compiledConfigFilePathForTest = resolve(projectBuiltDir, '._config_.json'); + +export const compiledConfigFilePath = fs.existsSync(compiledConfigFilePathForTest) + ? compiledConfigFilePathForTest + : resolve(projectBuiltDir, '.config.json'); export function loadConfig(): Config { - const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); + if (!fs.existsSync(compiledConfigFilePath)) { + throw new Error('Compiled configuration file not found. Try running \'pnpm compile-config\'.'); + } - const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json'); - const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json'); + const meta = JSON.parse(fs.readFileSync(resolve(projectBuiltDir, 'meta.json'), 'utf-8')); + + const frontendManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json')); + const frontendEmbedManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json')); const frontendManifest = frontendManifestExists ? - JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8')) - : { 'src/_boot_.ts': { file: 'src/_boot_.ts' } }; + JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'), 'utf-8')) + : { 'src/_boot_.ts': { file: null } }; const frontendEmbedManifest = frontendEmbedManifestExists ? - JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) - : { 'src/boot.ts': { file: 'src/boot.ts' } }; + JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'), 'utf-8')) + : { 'src/boot.ts': { file: null } }; - const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; + const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source; const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? ''); const version = meta.version; @@ -273,8 +283,17 @@ export function loadConfig(): Config { url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), socket: config.socket, + trustProxy: config.trustProxy ?? [ + '10.0.0.0/8', + '172.16.0.0/12', + '192.168.0.0/16', + '127.0.0.1/32', + '::1/128', + 'fc00::/7', + ], chmodSocket: config.chmodSocket, disableHsts: config.disableHsts, + enableIpRateLimit: config.enableIpRateLimit ?? true, host, hostname, scheme, @@ -300,7 +319,6 @@ export function loadConfig(): Config { proxySmtp: config.proxySmtp, proxyBypassHosts: config.proxyBypassHosts, allowedPrivateNetworks: config.allowedPrivateNetworks, - disallowExternalApRedirect: config.disallowExternalApRedirect ?? false, maxFileSize: config.maxFileSize ?? 262144000, clusterLimit: config.clusterLimit, outgoingAddress: config.outgoingAddress, @@ -313,8 +331,6 @@ export function loadConfig(): Config { relationshipJobPerSec: config.relationshipJobPerSec, deliverJobMaxAttempts: config.deliverJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts, - proxyRemoteFiles: config.proxyRemoteFiles, - signToActivityPubGet: config.signToActivityPubGet ?? true, mediaProxy: externalMediaProxy ?? internalMediaProxy, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, videoThumbnailGenerator: config.videoThumbnailGenerator ? @@ -336,7 +352,7 @@ export function loadConfig(): Config { function tryCreateUrl(url: string) { try { return new URL(url); - } catch (e) { + } catch (_) { throw new Error(`url="${url}" is not a valid URL.`); } } diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index f8e3eaf01f..5d668bc582 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -75,7 +75,7 @@ export class AccountMoveService { */ @bindThis public async moveFromLocal(src: MiLocalUser, dst: MiLocalUser | MiRemoteUser): Promise { - const srcUri = this.userEntityService.getUserUri(src); + const _srcUri = this.userEntityService.getUserUri(src); const dstUri = this.userEntityService.getUserUri(dst); // add movedToUri to indicate that the user has moved diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 4fc1193f32..8d2de89efd 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -9,87 +9,7 @@ import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; - -export const ACHIEVEMENT_TYPES = [ - 'notes1', - 'notes10', - 'notes100', - 'notes500', - 'notes1000', - 'notes5000', - 'notes10000', - 'notes20000', - 'notes30000', - 'notes40000', - 'notes50000', - 'notes60000', - 'notes70000', - 'notes80000', - 'notes90000', - 'notes100000', - 'login3', - 'login7', - 'login15', - 'login30', - 'login60', - 'login100', - 'login200', - 'login300', - 'login400', - 'login500', - 'login600', - 'login700', - 'login800', - 'login900', - 'login1000', - 'passedSinceAccountCreated1', - 'passedSinceAccountCreated2', - 'passedSinceAccountCreated3', - 'loggedInOnBirthday', - 'loggedInOnNewYearsDay', - 'noteClipped1', - 'noteFavorited1', - 'myNoteFavorited1', - 'profileFilled', - 'markedAsCat', - 'following1', - 'following10', - 'following50', - 'following100', - 'following300', - 'followers1', - 'followers10', - 'followers50', - 'followers100', - 'followers300', - 'followers500', - 'followers1000', - 'collectAchievements30', - 'viewAchievements3min', - 'iLoveMisskey', - 'foundTreasure', - 'client30min', - 'client60min', - 'noteDeletedWithin1min', - 'postedAtLateNight', - 'postedAt0min0sec', - 'selfQuote', - 'htl20npm', - 'viewInstanceChart', - 'outputHelloWorldOnScratchpad', - 'open3windows', - 'driveFolderCircularReference', - 'reactWithoutRead', - 'clickedClickHere', - 'justPlainLucky', - 'setNameToSyuilo', - 'cookieClicked', - 'brainDiver', - 'smashTestNotificationButton', - 'tutorialCompleted', - 'bubbleGameExplodingHead', - 'bubbleGameDoubleExplodingHead', -] as const; +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; @Injectable() export class AchievementService { diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 248a9b8979..cbae280030 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -7,11 +7,10 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Injectable } from '@nestjs/common'; -import * as nsfw from 'nsfwjs'; -import si from 'systeminformation'; import { Mutex } from 'async-mutex'; import fetch from 'node-fetch'; import { bindThis } from '@/decorators.js'; +import type { NSFWJS, PredictionType } from 'nsfwjs'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -21,7 +20,7 @@ let isSupportedCpu: undefined | boolean = undefined; @Injectable() export class AiService { - private model: nsfw.NSFWJS; + private model: NSFWJS; private modelLoadMutex: Mutex = new Mutex(); constructor( @@ -29,7 +28,7 @@ export class AiService { } @bindThis - public async detectSensitive(path: string): Promise { + public async detectSensitive(source: string | Buffer): Promise { try { if (isSupportedCpu === undefined) { isSupportedCpu = await this.computeIsSupportedCpu(); @@ -44,6 +43,7 @@ export class AiService { tf.env().global.fetch = fetch; if (this.model == null) { + const nsfw = await import('nsfwjs'); await this.modelLoadMutex.runExclusive(async () => { if (this.model == null) { this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); @@ -51,7 +51,7 @@ export class AiService { }); } - const buffer = await fs.promises.readFile(path); + const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source); const image = await tf.node.decodeImage(buffer, 3) as any; try { const predictions = await this.model.classify(image); @@ -83,6 +83,7 @@ export class AiService { @bindThis private async getCpuFlags(): Promise { + const si = await import('systeminformation'); const str = await si.cpuFlags(); return str.split(/\s+/); } diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index a9f6731977..f750ca212a 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -205,7 +205,7 @@ export class AnnouncementService { announcementId: announcementId, userId: user.id, }); - } catch (e) { + } catch (_) { return; } diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts deleted file mode 100644 index bd2749cb87..0000000000 --- a/packages/backend/src/core/AppLockService.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { promisify } from 'node:util'; -import { Inject, Injectable } from '@nestjs/common'; -import redisLock from 'redis-lock'; -import * as Redis from 'ioredis'; -import { DI } from '@/di-symbols.js'; -import { bindThis } from '@/decorators.js'; - -/** - * Retry delay (ms) for lock acquisition - */ -const retryDelay = 100; - -@Injectable() -export class AppLockService { - private lock: (key: string, timeout?: number, _?: (() => Promise) | undefined) => Promise<() => void>; - - constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, - ) { - this.lock = promisify(redisLock(this.redisClient, retryDelay)); - } - - /** - * Get AP Object lock - * @param uri AP object ID - * @param timeout Lock timeout (ms), The timeout releases previous lock. - * @returns Unlock function - */ - @bindThis - public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { - return this.lock(`ap-object:${uri}`, timeout); - } - - @bindThis - public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { - return this.lock(`chart-insert:${lockKey}`, timeout); - } -} diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts index 4efd6122b1..70a50a0175 100644 --- a/packages/backend/src/core/AvatarDecorationService.ts +++ b/packages/backend/src/core/AvatarDecorationService.ts @@ -39,7 +39,7 @@ export class AvatarDecorationService implements OnApplicationShutdown { const obj = JSON.parse(data); if (obj.channel === 'internal') { - const { type, body } = obj.message as GlobalEvents['internal']['payload']; + const { type, body: _ } = obj.message as GlobalEvents['internal']['payload']; switch (type) { case 'avatarDecorationCreated': case 'avatarDecorationUpdated': 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/ChatService.ts b/packages/backend/src/core/ChatService.ts index 9d294a80cb..5cd336a097 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -29,7 +29,7 @@ import { emojiRegex } from '@/misc/emoji-regex.js'; import { NotificationService } from '@/core/NotificationService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -const MAX_ROOM_MEMBERS = 30; +const MAX_ROOM_MEMBERS = 50; const MAX_REACTIONS_PER_MESSAGE = 100; const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/; @@ -331,6 +331,16 @@ export class ChatService { await redisPipeline.exec(); } + @bindThis + public async readAllChatMessages( + readerId: MiUser['id'], + ): Promise { + const redisPipeline = this.redisClient.pipeline(); + // TODO: newUserChatMessageExists とか newRoomChatMessageExists も消したい(けどキーの列挙が必要になって面倒) + redisPipeline.del(`newChatMessagesExists:${readerId}`); + await redisPipeline.exec(); + } + @bindThis public findMessageById(messageId: MiChatMessage['id']) { return this.chatMessagesRepository.findOneBy({ id: messageId }); @@ -578,6 +588,20 @@ export class ChatService { @bindThis public async deleteRoom(room: MiChatRoom, deleter?: MiUser) { + const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: room.id })).map(m => ({ + userId: m.userId, + })).concat({ // ownerはmembershipレコードを作らないため + userId: room.ownerId, + }); + + // 未読フラグ削除 + const redisPipeline = this.redisClient.pipeline(); + for (const membership of memberships) { + redisPipeline.del(`newRoomChatMessageExists:${membership.userId}:${room.id}`); + redisPipeline.srem(`newChatMessagesExists:${membership.userId}`, `room:${room.id}`); + } + await redisPipeline.exec(); + await this.chatRoomsRepository.delete(room.id); if (deleter) { @@ -709,6 +733,12 @@ export class ChatService { public async leaveRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) { const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId }); await this.chatRoomMembershipsRepository.delete(membership.id); + + // 未読フラグを消す (「既読にする」というわけでもないのでreadメソッドは使わないでおく) + const redisPipeline = this.redisClient.pipeline(); + redisPipeline.del(`newRoomChatMessageExists:${userId}:${roomId}`); + redisPipeline.srem(`newChatMessagesExists:${userId}`, `room:${roomId}`); + await redisPipeline.exec(); } @bindThis diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index e395c0dcfc..4345574198 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -15,12 +15,12 @@ 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'; import { AnnouncementService } from './AnnouncementService.js'; import { AntennaService } from './AntennaService.js'; -import { AppLockService } from './AppLockService.js'; import { AchievementService } from './AchievementService.js'; import { AvatarDecorationService } from './AvatarDecorationService.js'; import { CaptchaService } from './CaptchaService.js'; @@ -44,6 +44,7 @@ import { ModerationLogService } from './ModerationLogService.js'; import { NoteCreateService } from './NoteCreateService.js'; import { NoteDeleteService } from './NoteDeleteService.js'; import { NotePiningService } from './NotePiningService.js'; +import { NoteDraftService } from './NoteDraftService.js'; import { NotificationService } from './NotificationService.js'; import { PollService } from './PollService.js'; import { PushNotificationService } from './PushNotificationService.js'; @@ -78,6 +79,7 @@ import { ChatService } from './ChatService.js'; import { RegistryApiService } from './RegistryApiService.js'; import { ReversiService } from './ReversiService.js'; import { MahjongService } from './MahjongService.js'; +import { PageService } from './PageService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; @@ -119,6 +121,7 @@ import { RenoteMutingEntityService } from './entities/RenoteMutingEntityService. import { NoteEntityService } from './entities/NoteEntityService.js'; import { NoteFavoriteEntityService } from './entities/NoteFavoriteEntityService.js'; import { NoteReactionEntityService } from './entities/NoteReactionEntityService.js'; +import { NoteDraftEntityService } from './entities/NoteDraftEntityService.js'; import { NotificationEntityService } from './entities/NotificationEntityService.js'; import { PageEntityService } from './entities/PageEntityService.js'; import { PageLikeEntityService } from './entities/PageLikeEntityService.js'; @@ -139,7 +142,7 @@ import { ApLoggerService } from './activitypub/ApLoggerService.js'; import { ApMfmService } from './activitypub/ApMfmService.js'; import { ApRendererService } from './activitypub/ApRendererService.js'; import { ApRequestService } from './activitypub/ApRequestService.js'; -import { ApResolverService } from './activitypub/ApResolverService.js'; +import { ApResolverService, Resolver } from './activitypub/ApResolverService.js'; import { JsonLdService } from './activitypub/JsonLdService.js'; import { RemoteLoggerService } from './RemoteLoggerService.js'; import { RemoteUserResolveService } from './RemoteUserResolveService.js'; @@ -163,7 +166,6 @@ const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useEx const $AiService: Provider = { provide: 'AiService', useExisting: AiService }; const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExisting: AnnouncementService }; const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService }; -const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService }; const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService }; const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService }; const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService }; @@ -186,6 +188,7 @@ const $ModerationLogService: Provider = { provide: 'ModerationLogService', useEx const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService }; const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService }; const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService }; +const $NoteDraftService: Provider = { provide: 'NoteDraftService', useExisting: NoteDraftService }; const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService }; const $PollService: Provider = { provide: 'PollService', useExisting: PollService }; const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService }; @@ -222,10 +225,12 @@ 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 }; const $MahjongService: Provider = { provide: 'MahjongService', useExisting: MahjongService }; +const $PageService: Provider = { provide: 'PageService', useExisting: PageService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -268,6 +273,7 @@ const $RenoteMutingEntityService: Provider = { provide: 'RenoteMutingEntityServi const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService }; const $NoteFavoriteEntityService: Provider = { provide: 'NoteFavoriteEntityService', useExisting: NoteFavoriteEntityService }; const $NoteReactionEntityService: Provider = { provide: 'NoteReactionEntityService', useExisting: NoteReactionEntityService }; +const $NoteDraftEntityService: Provider = { provide: 'NoteDraftEntityService', useExisting: NoteDraftEntityService }; const $NotificationEntityService: Provider = { provide: 'NotificationEntityService', useExisting: NotificationEntityService }; const $PageEntityService: Provider = { provide: 'PageEntityService', useExisting: PageEntityService }; const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', useExisting: PageLikeEntityService }; @@ -314,7 +320,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting AiService, AnnouncementService, AntennaService, - AppLockService, AchievementService, AvatarDecorationService, CaptchaService, @@ -337,6 +342,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteCreateService, NoteDeleteService, NotePiningService, + NoteDraftService, NotificationService, PollService, SystemAccountService, @@ -373,10 +379,12 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FanoutTimelineService, FanoutTimelineEndpointService, ChannelFollowingService, + ChannelMutingService, ChatService, RegistryApiService, ReversiService, MahjongService, + PageService, ChartLoggerService, FederationChart, @@ -419,6 +427,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteEntityService, NoteFavoriteEntityService, NoteReactionEntityService, + NoteDraftEntityService, NotificationEntityService, PageEntityService, PageLikeEntityService, @@ -441,6 +450,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApRendererService, ApRequestService, ApResolverService, + Resolver, JsonLdService, RemoteLoggerService, RemoteUserResolveService, @@ -461,7 +471,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $AiService, $AnnouncementService, $AntennaService, - $AppLockService, $AchievementService, $AvatarDecorationService, $CaptchaService, @@ -484,6 +493,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteCreateService, $NoteDeleteService, $NotePiningService, + $NoteDraftService, $NotificationService, $PollService, $SystemAccountService, @@ -520,10 +530,12 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FanoutTimelineService, $FanoutTimelineEndpointService, $ChannelFollowingService, + $ChannelMutingService, $ChatService, $RegistryApiService, $ReversiService, $MahjongService, + $PageService, $ChartLoggerService, $FederationChart, @@ -566,6 +578,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteEntityService, $NoteFavoriteEntityService, $NoteReactionEntityService, + $NoteDraftEntityService, $NotificationEntityService, $PageEntityService, $PageLikeEntityService, @@ -609,7 +622,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting AiService, AnnouncementService, AntennaService, - AppLockService, AchievementService, AvatarDecorationService, CaptchaService, @@ -632,6 +644,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteCreateService, NoteDeleteService, NotePiningService, + NoteDraftService, NotificationService, PollService, SystemAccountService, @@ -668,10 +681,12 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FanoutTimelineService, FanoutTimelineEndpointService, ChannelFollowingService, + ChannelMutingService, ChatService, RegistryApiService, ReversiService, MahjongService, + PageService, FederationChart, NotesChart, @@ -713,6 +728,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteEntityService, NoteFavoriteEntityService, NoteReactionEntityService, + NoteDraftEntityService, NotificationEntityService, PageEntityService, PageLikeEntityService, @@ -735,6 +751,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApRendererService, ApRequestService, ApResolverService, + Resolver, JsonLdService, RemoteLoggerService, RemoteUserResolveService, @@ -755,7 +772,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $AiService, $AnnouncementService, $AntennaService, - $AppLockService, $AchievementService, $AvatarDecorationService, $CaptchaService, @@ -778,6 +794,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteCreateService, $NoteDeleteService, $NotePiningService, + $NoteDraftService, $NotificationService, $PollService, $SystemAccountService, @@ -813,10 +830,12 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FanoutTimelineService, $FanoutTimelineEndpointService, $ChannelFollowingService, + $ChannelMutingService, $ChatService, $RegistryApiService, $ReversiService, $MahjongService, + $PageService, $FederationChart, $NotesChart, @@ -858,6 +877,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteEntityService, $NoteFavoriteEntityService, $NoteReactionEntityService, + $NoteDraftEntityService, $NotificationEntityService, $PageEntityService, $PageLikeEntityService, diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 1550fe3d3c..816f83ec93 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -8,7 +8,7 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import sharp from 'sharp'; import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; -import { IsNull } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DI } from '@/di-symbols.js'; import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js'; @@ -469,13 +469,14 @@ export class DriveService { if (user && this.meta.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true; const info = await this.fileInfoService.getFileInfo(path, { + fileName: name, skipSensitiveDetection: skipNsfwCheck, sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる - this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : - this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : - this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : - this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : - 0.5, + this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : + this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : + this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : + this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : + 0.5, sensitiveThresholdForPorn: 0.75, enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos, }); @@ -515,22 +516,44 @@ export class DriveService { this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`); - //#region Check drive usage - if (user && !isLink) { - const usage = await this.driveFileEntityService.calcDriveUsageOf(user); + //#region Check drive usage and mime type + if (user != null && !isLink) { const isLocalUser = this.userEntityService.isLocalUser(user); + const isModerator = isLocalUser ? await this.roleService.isModerator(user) : false; + if (!isModerator) { + const policies = await this.roleService.getUserPolicies(user.id); - const policies = await this.roleService.getUserPolicies(user.id); - const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; - this.registerLogger.debug('drive capacity override applied'); - this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); - - // If usage limit exceeded - if (driveCapacity < usage + info.size) { - if (isLocalUser) { - throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); + const allowedMimeTypes = policies.uploadableFileTypes; + const isAllowed = allowedMimeTypes.some((mimeType) => { + if (mimeType === '*' || mimeType === '*/*') return true; + if (mimeType.endsWith('/*')) return info.type.mime.startsWith(mimeType.slice(0, -1)); + return info.type.mime === mimeType; + }); + if (!isAllowed) { + throw new IdentifiableError('bd71c601-f9b0-4808-9137-a330647ced9b', `Unallowed file type: ${info.type.mime}`); + } + + const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; + const maxFileSize = 1024 * 1024 * policies.maxFileSizeMb; + + if (maxFileSize < info.size) { + if (isLocalUser) { + throw new IdentifiableError('f9e4e5f3-4df4-40b5-b400-f236945f7073', 'Max file size exceeded.'); + } + } + + const usage = await this.driveFileEntityService.calcDriveUsageOf(user); + + this.registerLogger.debug('drive capacity override applied'); + this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); + + // If usage limit exceeded + if (driveCapacity < usage + info.size) { + if (isLocalUser) { + throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); + } + await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size); } - await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size); } } //#endregion @@ -713,6 +736,21 @@ export class DriveService { return fileObj; } + @bindThis + public async moveFiles(fileIds: MiDriveFile['id'][], folderId: MiDriveFolder['id'] | null, userId: MiUser['id']) { + const folder = folderId ? await this.driveFoldersRepository.findOneByOrFail({ + id: folderId, + userId: userId, + }) : null; + + await this.driveFilesRepository.update({ + id: In(fileIds), + userId: userId, + }, { + folderId: folder ? folder.id : null, + }); + } + @bindThis public async deleteFile(file: MiDriveFile, isExpired = false, deleter?: MiUser) { if (file.storedInternal) { @@ -768,14 +806,14 @@ export class DriveService { await Promise.all(promises); } - this.deletePostProcess(file, isExpired, deleter); + await this.deletePostProcess(file, isExpired, deleter); } @bindThis private async deletePostProcess(file: MiDriveFile, isExpired = false, deleter?: MiUser) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { - this.driveFilesRepository.update(file.id, { + await this.driveFilesRepository.update(file.id, { isLink: true, url: file.uri, thumbnailUrl: null, @@ -787,7 +825,7 @@ export class DriveService { webpublicAccessKey: 'webpublic-' + randomUUID(), }); } else { - this.driveFilesRepository.delete(file.id); + await this.driveFilesRepository.delete(file.id); } this.driveChart.update(file, false); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 45d7ea11e4..384704b252 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -145,7 +145,10 @@ export class EmailService { try { // TODO: htmlサニタイズ const info = await transporter.sendMail({ - from: this.meta.email!, + from: this.meta.name ? { + name: this.meta.name, + address: this.meta.email!, + } : this.meta.email!, to: to, subject: subject, text: text, @@ -363,7 +366,7 @@ export class EmailService { valid: true, reason: null, }; - } catch (error) { + } catch (_) { return { valid: false, reason: 'network', diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index ce8cc83dfd..e39d70d683 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -8,15 +8,21 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; +import type { MiMeta } from '@/models/Meta.js'; import { Packed } from '@/misc/json-schema.js'; import type { NotesRepository } from '@/models/_.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; 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; type TimelineOptions = { untilId: string | null, @@ -26,13 +32,16 @@ type TimelineOptions = { me?: { id: MiUser['id'] } | undefined | null, useDbFallback: boolean, redisTimelines: FanoutTimelineName[], - noteFilter?: (note: MiNote) => boolean, + noteFilter?: NoteFilter, alwaysIncludeMyNotes?: boolean; ignoreAuthorFromBlock?: boolean; ignoreAuthorFromMute?: boolean; + ignoreAuthorFromInstanceBlock?: boolean; + ignoreAuthorChannelFromMute?: boolean; excludeNoFiles?: boolean; excludeReplies?: boolean; excludePureRenotes: boolean; + ignoreAuthorFromUserSuspension?: boolean; dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise, }; @@ -42,9 +51,14 @@ export class FanoutTimelineEndpointService { @Inject(DI.notesRepository) private notesRepository: NotesRepository, + @Inject(DI.meta) + private meta: MiMeta, + private noteEntityService: NoteEntityService, private cacheService: CacheService, private fanoutTimelineService: FanoutTimelineService, + private utilityService: UtilityService, + private channelMutingService: ChannelMutingService, ) { } @@ -71,7 +85,7 @@ export class FanoutTimelineEndpointService { const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId; if (!shouldFallbackToDb) { - let filter = ps.noteFilter ?? (_note => true); + let filter = ps.noteFilter ?? (_note => true) as NoteFilter; if (ps.alwaysIncludeMyNotes && ps.me) { const me = ps.me; @@ -101,19 +115,50 @@ 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; filter = (note) => { if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; + if (isUserRelated(note.renote, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; + 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); + }; + } + + { + const parentFilter = filter; + filter = (note) => { + if (!ps.ignoreAuthorFromInstanceBlock) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, note.userHost)) return false; + } + if (note.userId !== note.renoteUserId && this.utilityService.isBlockedHost(this.meta.blockedHosts, note.renoteUserHost)) return false; + if (note.userId !== note.replyUserId && this.utilityService.isBlockedHost(this.meta.blockedHosts, note.replyUserHost)) return false; + + return parentFilter(note); + }; + } + + { + const parentFilter = filter; + filter = (note) => { + if (!ps.ignoreAuthorFromUserSuspension) { + if (note.user!.isSuspended) return false; + } + if (note.userId !== note.renoteUserId && note.renote?.user?.isSuspended) return false; + if (note.userId !== note.replyUserId && note.reply?.user?.isSuspended) return false; return parentFilter(note); }; @@ -160,7 +205,7 @@ export class FanoutTimelineEndpointService { return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit); } - private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise { + private async getAndFilterFromDb(noteIds: string[], noteFilter: NoteFilter, idCompare: (a: string, b: string) => number): Promise { const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index ce3af7c774..955f7035d7 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -5,9 +5,9 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import { JSDOM } from 'jsdom'; import tinycolor from 'tinycolor2'; import * as Redis from 'ioredis'; +import * as htmlParser from 'node-html-parser'; import type { MiInstance } from '@/models/Instance.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; @@ -15,7 +15,6 @@ import { LoggerService } from '@/core/LoggerService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; -import type { DOMWindow } from 'jsdom'; type NodeInfo = { openRegistrations?: unknown; @@ -59,7 +58,7 @@ export class FetchInstanceMetadataService { return await this.redisClient.set( `fetchInstanceMetadata:mutex:v2:${host}`, '1', 'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395 - 'GET' // 古い値を返す(なかったらnull) + 'GET', // 古い値を返す(なかったらnull) ); } @@ -181,15 +180,14 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchDom(instance: MiInstance): Promise { + private async fetchDom(instance: MiInstance): Promise { this.logger.info(`Fetching HTML of ${instance.host} ...`); const url = 'https://' + instance.host; const html = await this.httpRequestService.getHtml(url); - const { window } = new JSDOM(html); - const doc = window.document; + const doc = htmlParser.parse(html); return doc; } @@ -206,12 +204,12 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise { + private async fetchFaviconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null): Promise { const url = 'https://' + instance.host; if (doc) { // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 - const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.relList.contains('icon'))?.href; + const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.attributes.rel === 'icon')?.attributes.href; if (href) { return (new URL(href, url)).href; @@ -232,7 +230,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchIconUrl(instance: MiInstance, doc: Document | null, manifest: Record | null): Promise { + private async fetchIconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { const url = 'https://' + instance.host; return (new URL(manifest.icons[0].src, url)).href; @@ -246,9 +244,9 @@ export class FetchInstanceMetadataService { // https://github.com/misskey-dev/misskey/pull/8220/files/0ec4eba22a914e31b86874f12448f88b3e58dd5a#r796487559 const href = [ - links.find(link => link.relList.contains('apple-touch-icon-precomposed'))?.href, - links.find(link => link.relList.contains('apple-touch-icon'))?.href, - links.find(link => link.relList.contains('icon'))?.href, + links.find(link => link.attributes.rel?.split(/\s+/).includes('apple-touch-icon-precomposed'))?.attributes.href, + links.find(link => link.attributes.rel?.split(/\s+/).includes('apple-touch-icon'))?.attributes.href, + links.find(link => link.attributes.rel?.split(/\s+/).includes('icon'))?.attributes.href, ] .find(href => href); @@ -261,7 +259,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getThemeColor(info: NodeInfo | null, doc: Document | null, manifest: Record | null): Promise { + private async getThemeColor(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; if (themeColor) { @@ -273,7 +271,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record | null): Promise { + private async getSiteName(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeName === 'string') { return info.metadata.nodeName; @@ -298,7 +296,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getDescription(info: NodeInfo | null, doc: Document | null, manifest: Record | null): Promise { + private async getDescription(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeDescription === 'string') { return info.metadata.nodeDescription; diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index a295e81920..c7c9f8037d 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -20,6 +20,7 @@ import { AiService } from '@/core/AiService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; import type { PredictionType } from 'nsfwjs'; export type FileInfo = { @@ -64,6 +65,7 @@ export class FileInfoService { */ @bindThis public async getFileInfo(path: string, opts: { + fileName?: string | null; skipSensitiveDetection: boolean; sensitiveThreshold?: number; sensitiveThresholdForPorn?: number; @@ -76,6 +78,26 @@ export class FileInfoService { let type = await this.detectType(path); + if (type.mime === TYPE_OCTET_STREAM.mime && opts.fileName != null) { + const ext = opts.fileName.split('.').pop(); + if (ext === 'txt') { + type = { + mime: 'text/plain', + ext: 'txt', + }; + } else if (ext === 'csv') { + type = { + mime: 'text/csv', + ext: 'csv', + }; + } else if (ext === 'json') { + type = { + mime: 'application/json', + ext: 'json', + }; + } + } + // image dimensions let width: number | undefined; let height: number | undefined; @@ -183,16 +205,7 @@ export class FileInfoService { return [sensitive, porn]; } - if ([ - 'image/jpeg', - 'image/png', - 'image/webp', - ].includes(mime)) { - const result = await this.aiService.detectSensitive(source); - if (result) { - [sensitive, porn] = judgePrediction(result); - } - } else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { + if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { const [outDir, disposeOutDir] = await createTempDir(); try { const command = FFmpeg() @@ -260,6 +273,23 @@ export class FileInfoService { } finally { disposeOutDir(); } + } else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) { + /* + * tfjs-node は限られた画像形式しか受け付けないため、sharp で PNG に変換する + * せっかくなので内部処理で使われる最大サイズの299x299に事前にリサイズする + */ + const png = await (await sharpBmp(source, mime)) + .resize(299, 299, { + withoutEnlargement: false, + }) + .rotate() + .flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす + .png() + .toBuffer(); + const result = await this.aiService.detectSensitive(png); + if (result) { + [sensitive, porn] = judgePrediction(result); + } } return [sensitive, porn]; @@ -309,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'; @@ -438,12 +468,12 @@ export class FileInfoService { */ @bindThis private async detectImageSize(path: string): Promise<{ - width: number; - height: number; - wUnits: string; - hUnits: string; - orientation?: number; -}> { + width: number; + height: number; + wUnits: string; + hUnits: string; + orientation?: number; + }> { const readable = fs.createReadStream(path); const imageSize = await probeImageSize(readable); readable.destroy(); @@ -454,25 +484,13 @@ export class FileInfoService { * Calculate blurhash string of image */ @bindThis - private getBlurhash(path: string, type: string): Promise { - return new Promise(async (resolve, reject) => { - (await sharpBmp(path, type)) - .raw() - .ensureAlpha() - .resize(64, 64, { fit: 'inside' }) - .toBuffer((err, buffer, info) => { - if (err) return reject(err); - - let hash; - - try { - hash = blurhash.encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5); - } catch (e) { - return reject(e); - } - - resolve(hash); - }); - }); + private async getBlurhash(path: string, type: string): Promise { + const sharp = await sharpBmp(path, type); + const { data: buffer, info } = await sharp + .raw() + .ensureAlpha() + .resize(64, 64, { fit: 'inside' }) + .toBuffer({ resolveWithObject: true }); + return blurhash.encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5); } } diff --git a/packages/backend/src/core/FlashService.ts b/packages/backend/src/core/FlashService.ts index 2a98225382..8caffe9e45 100644 --- a/packages/backend/src/core/FlashService.ts +++ b/packages/backend/src/core/FlashService.ts @@ -4,8 +4,11 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { type FlashsRepository } from '@/models/_.js'; +import { type FlashLikesRepository, MiUser, type FlashsRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; /** * MisskeyPlay関係のService @@ -15,6 +18,11 @@ export class FlashService { constructor( @Inject(DI.flashsRepository) private flashRepository: FlashsRepository, + + @Inject(DI.flashLikesRepository) + private flashLikesRepository: FlashLikesRepository, + + private queryService: QueryService, ) { } @@ -37,4 +45,43 @@ export class FlashService { return await builder.getMany(); } + + public async myLikes(meId: MiUser['id'], opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number, search?: string | null }) { + const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate) + .andWhere('like.userId = :meId', { meId }) + .leftJoinAndSelect('like.flash', 'flash'); + + if (opts.search != null) { + for (const word of opts.search.trim().split(' ')) { + query.andWhere(new Brackets(qb => { + qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + })); + } + } + + const likes = await query + .limit(opts.limit) + .getMany(); + + return likes; + } + + public async search(searchQuery: string, opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number }) { + const query = this.queryService.makePaginationQuery(this.flashRepository.createQueryBuilder('flash'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate) + .andWhere('flash.visibility = \'public\''); + + for (const word of searchQuery.trim().split(' ')) { + query.andWhere(new Brackets(qb => { + qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + })); + } + + const result = await query + .limit(opts.limit) + .getMany(); + + return result; + } } diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index e7b2185ded..0d03a6fd61 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -39,11 +39,7 @@ export interface BroadcastTypes { emojis: Packed<'EmojiDetailed'>[]; }; emojiDeleted: { - emojis: { - id?: string; - name: string; - [other: string]: any; - }[]; + emojis: Packed<'EmojiDetailed'>[]; }; announcementCreated: { announcement: Packed<'Announcement'>; @@ -328,6 +324,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/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 3ddfe52045..5714bde8bf 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -6,6 +6,7 @@ import * as http from 'node:http'; import * as https from 'node:https'; import * as net from 'node:net'; +import * as stream from 'node:stream'; import ipaddr from 'ipaddr.js'; import CacheableLookup from 'cacheable-lookup'; import fetch from 'node-fetch'; @@ -26,12 +27,6 @@ export type HttpRequestSendOptions = { validators?: ((res: Response) => void)[]; }; -declare module 'node:http' { - interface Agent { - createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket; - } -} - class HttpRequestServiceAgent extends http.Agent { constructor( private config: Config, @@ -41,18 +36,24 @@ class HttpRequestServiceAgent extends http.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { - const socket = super.createConnection(options, callback) - .on('connect', () => { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { + const socket = super.createConnection(options, callback); + + if (socket == null) { + throw new Error('Failed to create socket'); + } + + socket.on('connect', () => { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { - if (address && ipaddr.isValid(address)) { - if (this.isPrivateIp(address)) { - socket.destroy(new Error(`Blocked address: ${address}`)); - } + if (address && ipaddr.isValid(address)) { + if (this.isPrivateIp(address)) { + socket.destroy(new Error(`Blocked address: ${address}`)); } } - }); + } + }); + return socket; } @@ -80,18 +81,24 @@ class HttpsRequestServiceAgent extends https.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { - const socket = super.createConnection(options, callback) - .on('connect', () => { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { + const socket = super.createConnection(options, callback); + + if (socket == null) { + throw new Error('Failed to create socket'); + } + + socket.on('connect', () => { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { - if (address && ipaddr.isValid(address)) { - if (this.isPrivateIp(address)) { - socket.destroy(new Error(`Blocked address: ${address}`)); - } + if (address && ipaddr.isValid(address)) { + if (this.isPrivateIp(address)) { + socket.destroy(new Error(`Blocked address: ${address}`)); } } - }); + } + }); + return socket; } diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index 6f978b34c8..6f60475442 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -34,6 +34,7 @@ export const webpDefault: sharp.WebpOptions = { smartSubsample: true, mixed: true, effort: 2, + loop: 0, }; export const avifDefault: sharp.AvifOptions = { diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 28d980f718..274966d921 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -5,26 +5,19 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import * as parse5 from 'parse5'; -import { type Document, type HTMLParagraphElement, Window, XMLSerializer } from 'happy-dom'; +import * as htmlParser from 'node-html-parser'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { intersperse } from '@/misc/prelude/array.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; -import type { DefaultTreeAdapterMap } from 'parse5'; +import { escapeHtml } from '@/misc/escape-html.js'; import type * as mfm from 'mfm-js'; -const treeAdapter = parse5.defaultTreeAdapter; -type Node = DefaultTreeAdapterMap['node']; -type ChildNode = DefaultTreeAdapterMap['childNode']; - const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; -export type Appender = (document: Document, body: HTMLParagraphElement) => void; - @Injectable() export class MfmService { constructor( @@ -40,68 +33,68 @@ export class MfmService { const normalizedHashtagNames = hashtagNames == null ? undefined : new Set(hashtagNames.map(x => normalizeForSearch(x))); - const dom = parse5.parseFragment(html); + const doc = htmlParser.parse(`
${html}
`); let text = ''; - for (const n of dom.childNodes) { + for (const n of doc.childNodes) { analyze(n); } return text.trim(); - function getText(node: Node): string { - if (treeAdapter.isTextNode(node)) return node.value; - if (!treeAdapter.isElementNode(node)) return ''; - if (node.nodeName === 'br') return '\n'; + function getText(node: htmlParser.Node): string { + if (node instanceof htmlParser.TextNode) return node.textContent; + if (!(node instanceof htmlParser.HTMLElement)) return ''; + if (node.tagName === 'BR') return '\n'; - if (node.childNodes) { + if (node.childNodes != null) { return node.childNodes.map(n => getText(n)).join(''); } return ''; } - function appendChildren(childNodes: ChildNode[]): void { - if (childNodes) { + function analyzeChildren(childNodes: htmlParser.Node[] | null): void { + if (childNodes != null) { for (const n of childNodes) { analyze(n); } } } - function analyze(node: Node) { - if (treeAdapter.isTextNode(node)) { - text += node.value; + function analyze(node: htmlParser.Node) { + if (node instanceof htmlParser.TextNode) { + text += node.textContent; return; } // Skip comment or document type node - if (!treeAdapter.isElementNode(node)) { + if (!(node instanceof htmlParser.HTMLElement)) { return; } - switch (node.nodeName) { - case 'br': { + switch (node.tagName) { + case 'BR': { text += '\n'; break; } - case 'a': { + case 'A': { const txt = getText(node); - const rel = node.attrs.find(x => x.name === 'rel'); - const href = node.attrs.find(x => x.name === 'href'); + const rel = node.attributes.rel; + const href = node.attributes.href; // ハッシュタグ - if (normalizedHashtagNames && href && normalizedHashtagNames.has(normalizeForSearch(txt))) { + if (normalizedHashtagNames && href != null && normalizedHashtagNames.has(normalizeForSearch(txt))) { text += txt; // メンション - } else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) { + } else if (txt.startsWith('@') && !(rel != null && rel.startsWith('me '))) { const part = txt.split('@'); if (part.length === 2 && href) { //#region ホスト名部分が省略されているので復元する - const acct = `${txt}@${(new URL(href.value)).hostname}`; + const acct = `${txt}@${(new URL(href)).hostname}`; text += acct; //#endregion } else if (part.length === 3) { @@ -116,17 +109,17 @@ export class MfmService { if (!href) { return txt; } - if (!txt || txt === href.value) { // #6383: Missing text node - if (href.value.match(urlRegexFull)) { - return href.value; + if (!txt || txt === href) { // #6383: Missing text node + if (href.match(urlRegexFull)) { + return href; } else { - return `<${href.value}>`; + return `<${href}>`; } } - if (href.value.match(urlRegex) && !href.value.match(urlRegexFull)) { - return `[${txt}](<${href.value}>)`; // #6846 + if (href.match(urlRegex) && !href.match(urlRegexFull)) { + return `[${txt}](<${href}>)`; // #6846 } else { - return `[${txt}](${href.value})`; + return `[${txt}](${href})`; } }; @@ -135,60 +128,64 @@ export class MfmService { break; } - case 'h1': { + case 'H1': { text += '【'; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); text += '】\n'; break; } - case 'b': - case 'strong': { + case 'B': + case 'STRONG': { text += '**'; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); text += '**'; break; } - case 'small': { + case 'SMALL': { text += ''; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); text += ''; break; } - case 's': - case 'del': { + case 'S': + case 'DEL': { text += '~~'; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); text += '~~'; break; } - case 'i': - case 'em': { + case 'I': + case 'EM': { text += ''; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); text += ''; break; } - case 'ruby': { + case 'RUBY': { let ruby: [string, string][] = []; for (const child of node.childNodes) { - if (child.nodeName === 'rp') { + if ((child instanceof htmlParser.TextNode) && !/\s|\[|\]/.test(child.textContent)) { + ruby.push([child.textContent, '']); continue; } - if (treeAdapter.isTextNode(child) && !/\s|\[|\]/.test(child.value)) { - ruby.push([child.value, '']); + + if (!(child instanceof htmlParser.HTMLElement)) continue; + + if (child.tagName === 'RP') { continue; } - if (child.nodeName === 'rt' && ruby.length > 0) { + + if (child.tagName === 'RT' && ruby.length > 0) { const rt = getText(child); if (/\s|\[|\]/.test(rt)) { // If any space is included in rt, it is treated as a normal text ruby = []; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); break; } else { ruby.at(-1)![1] = rt; @@ -197,7 +194,7 @@ export class MfmService { } // If any other element is included in ruby, it is treated as a normal text ruby = []; - appendChildren(node.childNodes); + analyzeChildren(node.childNodes); break; } for (const [base, rt] of ruby) { @@ -207,26 +204,30 @@ export class MfmService { } // block code (
)
-				case 'pre': {
-					if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
+				case 'PRE': {
+					if (node.childNodes.length === 1 && (node.childNodes[0] instanceof htmlParser.HTMLElement) && node.childNodes[0].tagName === 'CODE') {
 						text += '\n```\n';
 						text += getText(node.childNodes[0]);
 						text += '\n```\n';
+					} else if (node.childNodes.length === 1 && (node.childNodes[0] instanceof htmlParser.TextNode) && node.childNodes[0].textContent.startsWith('') && node.childNodes[0].textContent.endsWith('')) {
+						text += '\n```\n';
+						text += node.childNodes[0].textContent.slice(6, -7);
+						text += '\n```\n';
 					} else {
-						appendChildren(node.childNodes);
+						analyzeChildren(node.childNodes);
 					}
 					break;
 				}
 
 				// inline code ()
-				case 'code': {
+				case 'CODE': {
 					text += '`';
-					appendChildren(node.childNodes);
+					analyzeChildren(node.childNodes);
 					text += '`';
 					break;
 				}
 
-				case 'blockquote': {
+				case 'BLOCKQUOTE': {
 					const t = getText(node);
 					if (t) {
 						text += '\n> ';
@@ -235,33 +236,33 @@ export class MfmService {
 					break;
 				}
 
-				case 'p':
-				case 'h2':
-				case 'h3':
-				case 'h4':
-				case 'h5':
-				case 'h6': {
+				case 'P':
+				case 'H2':
+				case 'H3':
+				case 'H4':
+				case 'H5':
+				case 'H6': {
 					text += '\n\n';
-					appendChildren(node.childNodes);
+					analyzeChildren(node.childNodes);
 					break;
 				}
 
 				// other block elements
-				case 'div':
-				case 'header':
-				case 'footer':
-				case 'article':
-				case 'li':
-				case 'dt':
-				case 'dd': {
+				case 'DIV':
+				case 'HEADER':
+				case 'FOOTER':
+				case 'ARTICLE':
+				case 'LI':
+				case 'DT':
+				case 'DD': {
 					text += '\n';
-					appendChildren(node.childNodes);
+					analyzeChildren(node.childNodes);
 					break;
 				}
 
 				default:	// includes inline elements
 				{
-					appendChildren(node.childNodes);
+					analyzeChildren(node.childNodes);
 					break;
 				}
 			}
@@ -269,52 +270,35 @@ export class MfmService {
 	}
 
 	@bindThis
-	public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], additionalAppenders: Appender[] = []) {
+	public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], extraHtml: string | null = null) {
 		if (nodes == null) {
 			return null;
 		}
 
-		const { happyDOM, window } = new Window();
-
-		const doc = window.document;
-
-		const body = doc.createElement('p');
-
-		function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
-			if (children) {
-				for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child);
-			}
+		function toHtml(children?: mfm.MfmNode[]): string {
+			if (children == null) return '';
+			return children.map(x => handlers[x.type](x)).join('');
 		}
 
 		function fnDefault(node: mfm.MfmFn) {
-			const el = doc.createElement('i');
-			appendChildren(node.children, el);
-			return el;
+			return `${toHtml(node.children)}`;
 		}
 
-		const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any } = {
+		const handlers = {
 			bold: (node) => {
-				const el = doc.createElement('b');
-				appendChildren(node.children, el);
-				return el;
+				return `${toHtml(node.children)}`;
 			},
 
 			small: (node) => {
-				const el = doc.createElement('small');
-				appendChildren(node.children, el);
-				return el;
+				return `${toHtml(node.children)}`;
 			},
 
 			strike: (node) => {
-				const el = doc.createElement('del');
-				appendChildren(node.children, el);
-				return el;
+				return `${toHtml(node.children)}`;
 			},
 
 			italic: (node) => {
-				const el = doc.createElement('i');
-				appendChildren(node.children, el);
-				return el;
+				return `${toHtml(node.children)}`;
 			},
 
 			fn: (node) => {
@@ -323,11 +307,8 @@ export class MfmService {
 						const text = node.children[0].type === 'text' ? node.children[0].props.text : '';
 						try {
 							const date = new Date(parseInt(text, 10) * 1000);
-							const el = doc.createElement('time');
-							el.setAttribute('datetime', date.toISOString());
-							el.textContent = date.toISOString();
-							return el;
-						} catch (err) {
+							return ``;
+						} catch (_) {
 							return fnDefault(node);
 						}
 					}
@@ -336,21 +317,9 @@ export class MfmService {
 						if (node.children.length === 1) {
 							const child = node.children[0];
 							const text = child.type === 'text' ? child.props.text : '';
-							const rubyEl = doc.createElement('ruby');
-							const rtEl = doc.createElement('rt');
 
-							// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
-							const rpStartEl = doc.createElement('rp');
-							rpStartEl.appendChild(doc.createTextNode('('));
-							const rpEndEl = doc.createElement('rp');
-							rpEndEl.appendChild(doc.createTextNode(')'));
-
-							rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
-							rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
-							rubyEl.appendChild(rpStartEl);
-							rubyEl.appendChild(rtEl);
-							rubyEl.appendChild(rpEndEl);
-							return rubyEl;
+							// ruby未対応のHTMLサニタイザーを通したときにルビが「対象テキスト(ルビテキスト)」にフォールバックするようにする
+							return `${escapeHtml(text.split(' ')[0])}(${escapeHtml(text.split(' ')[1])})`;
 						} else {
 							const rt = node.children.at(-1);
 
@@ -359,21 +328,9 @@ export class MfmService {
 							}
 
 							const text = rt.type === 'text' ? rt.props.text : '';
-							const rubyEl = doc.createElement('ruby');
-							const rtEl = doc.createElement('rt');
 
-							// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
-							const rpStartEl = doc.createElement('rp');
-							rpStartEl.appendChild(doc.createTextNode('('));
-							const rpEndEl = doc.createElement('rp');
-							rpEndEl.appendChild(doc.createTextNode(')'));
-
-							appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
-							rtEl.appendChild(doc.createTextNode(text.trim()));
-							rubyEl.appendChild(rpStartEl);
-							rubyEl.appendChild(rtEl);
-							rubyEl.appendChild(rpEndEl);
-							return rubyEl;
+							// ruby未対応のHTMLサニタイザーを通したときにルビが「対象テキスト(ルビテキスト)」にフォールバックするようにする
+							return `${toHtml(node.children.slice(0, node.children.length - 1))}(${escapeHtml(text.trim())})`;
 						}
 					}
 
@@ -384,125 +341,98 @@ export class MfmService {
 			},
 
 			blockCode: (node) => {
-				const pre = doc.createElement('pre');
-				const inner = doc.createElement('code');
-				inner.textContent = node.props.code;
-				pre.appendChild(inner);
-				return pre;
+				return `
${escapeHtml(node.props.code)}
`; }, center: (node) => { - const el = doc.createElement('div'); - appendChildren(node.children, el); - return el; + return `
${toHtml(node.children)}
`; }, emojiCode: (node) => { - return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); + return `\u200B:${escapeHtml(node.props.name)}:\u200B`; }, unicodeEmoji: (node) => { - return doc.createTextNode(node.props.emoji); + return node.props.emoji; }, hashtag: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); - a.textContent = `#${node.props.hashtag}`; - a.setAttribute('rel', 'tag'); - return a; + return ``; }, inlineCode: (node) => { - const el = doc.createElement('code'); - el.textContent = node.props.code; - return el; + return `${escapeHtml(node.props.code)}`; }, mathInline: (node) => { - const el = doc.createElement('code'); - el.textContent = node.props.formula; - return el; + return `${escapeHtml(node.props.formula)}`; }, mathBlock: (node) => { - const el = doc.createElement('code'); - el.textContent = node.props.formula; - return el; + return `
${escapeHtml(node.props.formula)}
`; }, link: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', node.props.url); - appendChildren(node.children, a); - return a; + try { + const url = new URL(node.props.url); + return `${toHtml(node.children)}`; + } catch (_) { + return `[${toHtml(node.children)}](${escapeHtml(node.props.url)})`; + } }, mention: (node) => { - const a = doc.createElement('a'); const { username, host, acct } = node.props; const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase()); - a.setAttribute('href', remoteUserInfo + const href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) - : `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`); - a.className = 'u-url mention'; - a.textContent = acct; - return a; + : `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`; + try { + const url = new URL(href); + return `${escapeHtml(acct)}`; + } catch (_) { + return escapeHtml(acct); + } }, quote: (node) => { - const el = doc.createElement('blockquote'); - appendChildren(node.children, el); - return el; + return `
${toHtml(node.children)}
`; }, text: (node) => { if (!node.props.text.match(/[\r\n]/)) { - return doc.createTextNode(node.props.text); + return escapeHtml(node.props.text); } - const el = doc.createElement('span'); - const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x)); + let html = ''; - for (const x of intersperse('br', nodes)) { - el.appendChild(x === 'br' ? doc.createElement('br') : x); + const lines = node.props.text.split(/\r\n|\r|\n/).map(x => escapeHtml(x)); + + for (const x of intersperse('br', lines)) { + html += x === 'br' ? '
' : x; } - return el; + return html; }, url: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', node.props.url); - a.textContent = node.props.url; - return a; + try { + const url = new URL(node.props.url); + return `${escapeHtml(node.props.url)}`; + } catch (_) { + return escapeHtml(node.props.url); + } }, search: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', `https://www.google.com/search?q=${node.props.query}`); - a.textContent = node.props.content; - return a; + return `${escapeHtml(node.props.content)}`; }, plain: (node) => { - const el = doc.createElement('span'); - appendChildren(node.children, el); - return el; + return `${toHtml(node.children)}`; }, - }; + } satisfies { [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => string } as { [K in mfm.MfmNode['type']]: (node: mfm.MfmNode) => string }; - appendChildren(nodes, body); - - for (const additionalAppender of additionalAppenders) { - additionalAppender(doc, body); - } - - // Remove the unnecessary namespace - const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*

/, '

'); - - happyDOM.close().catch(err => {}); - - return serialized; + return `${toHtml(nodes)}${extraHtml ?? ''}`; } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 469426f87e..748f2cbad9 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -13,7 +13,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js'; -import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { BlockingsRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -56,6 +56,7 @@ import { trackPromise } from '@/misc/promise-tracker.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { CacheService } from '@/core/CacheService.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -192,6 +193,12 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.channelFollowingsRepository) private channelFollowingsRepository: ChannelFollowingsRepository, + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private idService: IdService, @@ -221,6 +228,167 @@ export class NoteCreateService implements OnApplicationShutdown { this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); } + @bindThis + public async fetchAndCreate(user: { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + isCat: MiUser['isCat']; + }, data: { + createdAt: Date; + replyId: MiNote['id'] | null; + renoteId: MiNote['id'] | null; + fileIds: MiDriveFile['id'][]; + text: string | null; + cw: string | null; + visibility: string; + visibleUserIds: MiUser['id'][]; + channelId: MiChannel['id'] | null; + localOnly: boolean; + reactionAcceptance: MiNote['reactionAcceptance']; + poll: IPoll | null; + apMentions?: MinimumUser[] | null; + apHashtags?: string[] | null; + apEmojis?: string[] | null; + }): Promise { + const visibleUsers = data.visibleUserIds.length > 0 ? await this.usersRepository.findBy({ + id: In(data.visibleUserIds), + }) : []; + + let files: MiDriveFile[] = []; + if (data.fileIds.length > 0) { + files = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: user.id, + fileIds: data.fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds: data.fileIds }) + .getMany(); + + if (files.length !== data.fileIds.length) { + throw new IdentifiableError('801c046c-5bf5-4234-ad2b-e78fc20a2ac7', 'No such file'); + } + } + + let renote: MiNote | null = null; + if (data.renoteId != null) { + // Fetch renote to note + renote = await this.notesRepository.findOne({ + where: { id: data.renoteId }, + relations: ['user', 'renote', 'reply'], + }); + + if (renote == null) { + throw new IdentifiableError('53983c56-e163-45a6-942f-4ddc485d4290', 'No such renote target'); + } else if (isRenote(renote) && !isQuote(renote)) { + throw new IdentifiableError('bde24c37-121f-4e7d-980d-cec52f599f02', 'Cannot renote pure renote'); + } + + // Check blocking + if (renote.userId !== user.id) { + const blockExist = await this.blockingsRepository.exists({ + where: { + blockerId: renote.userId, + blockeeId: user.id, + }, + }); + if (blockExist) { + throw new IdentifiableError('2b4fe776-4414-4a2d-ae39-f3418b8fd4d3', 'You have been blocked by the user'); + } + } + + if (renote.visibility === 'followers' && renote.userId !== user.id) { + // 他人のfollowers noteはreject + throw new IdentifiableError('90b9d6f0-893a-4fef-b0f1-e9a33989f71a', 'Renote target visibility'); + } else if (renote.visibility === 'specified') { + // specified / direct noteはreject + throw new IdentifiableError('48d7a997-da5c-4716-b3c3-92db3f37bf7d', 'Renote target visibility'); + } + + if (renote.channelId && renote.channelId !== data.channelId) { + // チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック + // リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する + const renoteChannel = await this.channelsRepository.findOneBy({ id: renote.channelId }); + if (renoteChannel == null) { + // リノートしたいノートが書き込まれているチャンネルが無い + throw new IdentifiableError('b060f9a6-8909-4080-9e0b-94d9fa6f6a77', 'No such channel'); + } else if (!renoteChannel.allowRenoteToExternal) { + // リノート作成のリクエストだが、対象チャンネルがリノート禁止だった場合 + throw new IdentifiableError('7e435f4a-780d-4cfc-a15a-42519bd6fb67', 'Channel does not allow renote to external'); + } + } + } + + let reply: MiNote | null = null; + if (data.replyId != null) { + // Fetch reply + reply = await this.notesRepository.findOne({ + where: { id: data.replyId }, + relations: ['user'], + }); + + if (reply == null) { + throw new IdentifiableError('60142edb-1519-408e-926d-4f108d27bee0', 'No such reply target'); + } else if (isRenote(reply) && !isQuote(reply)) { + throw new IdentifiableError('f089e4e2-c0e7-4f60-8a23-e5a6bf786b36', 'Cannot reply to pure renote'); + } else if (!await this.noteEntityService.isVisibleForMe(reply, user.id)) { + throw new IdentifiableError('11cd37b3-a411-4f77-8633-c580ce6a8dce', 'No such reply target'); + } else if (reply.visibility === 'specified' && data.visibility !== 'specified') { + throw new IdentifiableError('ced780a1-2012-4caf-bc7e-a95a291294cb', 'Cannot reply to specified note with different visibility'); + } + + // Check blocking + if (reply.userId !== user.id) { + const blockExist = await this.blockingsRepository.exists({ + where: { + blockerId: reply.userId, + blockeeId: user.id, + }, + }); + if (blockExist) { + throw new IdentifiableError('b0df6025-f2e8-44b4-a26a-17ad99104612', 'You have been blocked by the user'); + } + } + } + + if (data.poll) { + if (data.poll.expiresAt != null) { + if (data.poll.expiresAt.getTime() < Date.now()) { + throw new IdentifiableError('0c11c11e-0c8d-48e7-822c-76ccef660068', 'Poll expiration must be future time'); + } + } + } + + let channel: MiChannel | null = null; + if (data.channelId != null) { + channel = await this.channelsRepository.findOneBy({ id: data.channelId, isArchived: false }); + + if (channel == null) { + throw new IdentifiableError('bfa3905b-25f5-4894-b430-da331a490e4b', 'No such channel'); + } + } + + return this.create(user, { + createdAt: data.createdAt, + files: files, + poll: data.poll, + text: data.text, + reply, + renote, + cw: data.cw, + localOnly: data.localOnly, + reactionAcceptance: data.reactionAcceptance, + visibility: data.visibility, + visibleUsers, + channel, + apMentions: data.apMentions, + apHashtags: data.apHashtags, + apEmojis: data.apEmojis, + }); + } + @bindThis public async create(user: { id: MiUser['id']; @@ -421,7 +589,7 @@ export class NoteCreateService implements OnApplicationShutdown { emojis, userId: user.id, localOnly: data.localOnly!, - reactionAcceptance: data.reactionAcceptance, + reactionAcceptance: data.reactionAcceptance ?? null, visibility: data.visibility as any, visibleUserIds: data.visibility === 'specified' ? data.visibleUsers @@ -436,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, }); @@ -483,7 +652,11 @@ export class NoteCreateService implements OnApplicationShutdown { await this.notesRepository.insert(insert); } - return insert; + return { + ...insert, + reply: data.reply ?? null, + renote: data.renote ?? null, + }; } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index e394506a44..af1f0eda9a 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -62,7 +62,6 @@ export class NoteDeleteService { */ async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) { const deletedAt = new Date(); - const cascadingNotes = await this.findCascadingNotes(note); if (note.replyId) { await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1); @@ -90,15 +89,6 @@ export class NoteDeleteService { this.deliverToConcerned(user, note, content); } - - // also deliver delete activity to cascaded notes - const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes - for (const cascadingNote of federatedLocalCascadingNotes) { - if (!cascadingNote.user) continue; - if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; - const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); - this.deliverToConcerned(cascadingNote.user, cascadingNote, content); - } //#endregion this.notesChart.update(note, false); @@ -118,9 +108,6 @@ export class NoteDeleteService { } } - for (const cascadingNote of cascadingNotes) { - this.searchService.unindexNote(cascadingNote); - } this.searchService.unindexNote(note); await this.notesRepository.delete({ @@ -140,29 +127,6 @@ export class NoteDeleteService { } } - @bindThis - private async findCascadingNotes(note: MiNote): Promise { - const recursive = async (noteId: string): Promise => { - const query = this.notesRepository.createQueryBuilder('note') - .where('note.replyId = :noteId', { noteId }) - .orWhere(new Brackets(q => { - q.where('note.renoteId = :noteId', { noteId }) - .andWhere('note.text IS NOT NULL'); - })) - .leftJoinAndSelect('note.user', 'user'); - const replies = await query.getMany(); - - return [ - replies, - ...await Promise.all(replies.map(reply => recursive(reply.id))), - ].flat(); - }; - - const cascadingNotes: MiNote[] = await recursive(note.id); - - return cascadingNotes; - } - @bindThis private async getMentionedRemoteUsers(note: MiNote) { const where = [] as any[]; diff --git a/packages/backend/src/core/NoteDraftService.ts b/packages/backend/src/core/NoteDraftService.ts new file mode 100644 index 0000000000..e144138c2c --- /dev/null +++ b/packages/backend/src/core/NoteDraftService.ts @@ -0,0 +1,339 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { MiNoteDraft, NoteDraftsRepository, MiNote, MiDriveFile, MiChannel, UsersRepository, DriveFilesRepository, NotesRepository, BlockingsRepository, ChannelsRepository } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { isRenote, isQuote } from '@/misc/is-renote.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { QueueService } from '@/core/QueueService.js'; + +export type NoteDraftOptions = Omit; + +@Injectable() +export class NoteDraftService { + constructor( + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.noteDraftsRepository) + private noteDraftsRepository: NoteDraftsRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private roleService: RoleService, + private idService: IdService, + private noteEntityService: NoteEntityService, + private queueService: QueueService, + ) { + } + + @bindThis + public async get(me: MiLocalUser, draftId: MiNoteDraft['id']): Promise { + const draft = await this.noteDraftsRepository.findOneBy({ + id: draftId, + userId: me.id, + }); + + return draft; + } + + @bindThis + public async create(me: MiLocalUser, data: NoteDraftOptions): Promise { + //#region check draft limit + const policies = await this.roleService.getUserPolicies(me.id); + + const currentCount = await this.noteDraftsRepository.countBy({ + userId: me.id, + }); + if (currentCount >= policies.noteDraftLimit) { + throw new IdentifiableError('9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', 'Too many drafts'); + } + + if (data.isActuallyScheduled) { + const currentScheduledCount = await this.noteDraftsRepository.countBy({ + userId: me.id, + isActuallyScheduled: true, + }); + if (currentScheduledCount >= policies.scheduledNoteLimit) { + throw new IdentifiableError('c3275f19-4558-4c59-83e1-4f684b5fab66', 'Too many scheduled notes'); + } + } + //#endregion + + await this.validate(me, data); + + const draft = await this.noteDraftsRepository.insertOne({ + ...data, + id: this.idService.gen(), + userId: me.id, + }); + + if (draft.scheduledAt && draft.isActuallyScheduled) { + this.schedule(draft); + } + + return draft; + } + + @bindThis + public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: Partial): Promise { + const draft = await this.noteDraftsRepository.findOneBy({ + id: draftId, + userId: me.id, + }); + + if (draft == null) { + throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); + } + + //#region check draft limit + const policies = await this.roleService.getUserPolicies(me.id); + + if (!draft.isActuallyScheduled && data.isActuallyScheduled) { + const currentScheduledCount = await this.noteDraftsRepository.countBy({ + userId: me.id, + isActuallyScheduled: true, + }); + if (currentScheduledCount >= policies.scheduledNoteLimit) { + throw new IdentifiableError('bacdf856-5c51-4159-b88a-804fa5103be5', 'Too many scheduled notes'); + } + } + //#endregion + + await this.validate(me, data); + + const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update() + .set(data) + .where('id = :id', { id: draftId }) + .returning('*') + .execute() + .then((response) => response.raw[0]); + + this.clearSchedule(draftId).then(() => { + if (updatedDraft.scheduledAt != null && updatedDraft.isActuallyScheduled) { + this.schedule(updatedDraft); + } + }); + + return updatedDraft; + } + + @bindThis + public async delete(me: MiLocalUser, draftId: MiNoteDraft['id']): Promise { + const draft = await this.noteDraftsRepository.findOneBy({ + id: draftId, + userId: me.id, + }); + + if (draft == null) { + throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); + } + + await this.noteDraftsRepository.delete(draft.id); + + this.clearSchedule(draftId); + } + + @bindThis + public async getDraft(me: MiLocalUser, draftId: MiNoteDraft['id']): Promise { + const draft = await this.noteDraftsRepository.findOneBy({ + id: draftId, + userId: me.id, + }); + + if (draft == null) { + throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); + } + + return draft; + } + + @bindThis + public async validate( + me: MiLocalUser, + data: Partial, + ): Promise { + if (data.isActuallyScheduled) { + if (data.scheduledAt == null) { + throw new IdentifiableError('94a89a43-3591-400a-9c17-dd166e71fdfa', 'scheduledAt is required when isActuallyScheduled is true'); + } else if (data.scheduledAt.getTime() < Date.now()) { + throw new IdentifiableError('b34d0c1b-996f-4e34-a428-c636d98df457', 'scheduledAt must be in the future'); + } + } + + if (data.pollExpiresAt != null) { + if (data.pollExpiresAt.getTime() < Date.now()) { + throw new IdentifiableError('04da457d-b083-4055-9082-955525eda5a5', 'Cannot create expired poll'); + } + } + + //#region visibleUsers + let _visibleUsers: MiUser[] = []; + if (data.visibleUserIds != null && data.visibleUserIds.length > 0) { + _visibleUsers = await this.usersRepository.findBy({ + id: In(data.visibleUserIds), + }); + } + //#endregion + + //#region files + let files: MiDriveFile[] = []; + const fileIds = data.fileIds ?? null; + if (fileIds != null && fileIds.length > 0) { + files = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: me.id, + fileIds: fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds }) + .getMany(); + + if (files.length !== fileIds.length) { + throw new IdentifiableError('b6992544-63e7-67f0-fa7f-32444b1b5306', 'No such drive file'); + } + } + //#endregion + + //#region renote + let renote: MiNote | null = null; + if (data.renoteId != null) { + renote = await this.notesRepository.findOneBy({ id: data.renoteId }); + + if (renote == null) { + throw new IdentifiableError('64929870-2540-4d11-af41-3b484d78c956', 'No such renote'); + } else if (isRenote(renote) && !isQuote(renote)) { + throw new IdentifiableError('76cc5583-5a14-4ad3-8717-0298507e32db', 'Cannot renote'); + } + + // Check blocking + if (renote.userId !== me.id) { + const blockExist = await this.blockingsRepository.exists({ + where: { + blockerId: renote.userId, + blockeeId: me.id, + }, + }); + if (blockExist) { + throw new IdentifiableError('075ca298-e6e7-485a-b570-51a128bb5168', 'You have been blocked by the user'); + } + } + + if (renote.visibility === 'followers' && renote.userId !== me.id) { + // 他人のfollowers noteはreject + throw new IdentifiableError('81eb8188-aea1-4e35-9a8f-3334a3be9855', 'Cannot Renote Due to Visibility'); + } else if (renote.visibility === 'specified') { + // specified / direct noteはreject + throw new IdentifiableError('81eb8188-aea1-4e35-9a8f-3334a3be9855', 'Cannot Renote Due to Visibility'); + } + + if (renote.channelId && renote.channelId !== data.channelId) { + // チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック + // リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する + const renoteChannel = await this.channelsRepository.findOneBy({ id: renote.channelId }); + if (renoteChannel == null) { + // リノートしたいノートが書き込まれているチャンネルがない + throw new IdentifiableError('6815399a-6f13-4069-b60d-ed5156249d12', 'No such channel'); + } else if (!renoteChannel.allowRenoteToExternal) { + // リノート作成のリクエストだが、対象チャンネルがリノート禁止だった場合 + throw new IdentifiableError('ed1952ac-2d26-4957-8b30-2deda76bedf7', 'Cannot Renote to External'); + } + } + } + //#endregion + + //#region reply + let reply: MiNote | null = null; + if (data.replyId != null) { + // Fetch reply + reply = await this.notesRepository.findOneBy({ id: data.replyId }); + + if (reply == null) { + throw new IdentifiableError('c4721841-22fc-4bb7-ad3d-897ef1d375b5', 'No such reply'); + } else if (isRenote(reply) && !isQuote(reply)) { + throw new IdentifiableError('e6c10b57-2c09-4da3-bd4d-eda05d51d140', 'Cannot reply To Pure Renote'); + } else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) { + throw new IdentifiableError('593c323c-6b6a-4501-a25c-2f36bd2a93d6', 'Cannot reply To Invisible Note'); + } else if (reply.visibility === 'specified' && data.visibility !== 'specified') { + throw new IdentifiableError('215dbc76-336c-4d2a-9605-95766ba7dab0', 'Cannot reply To Specified Note With Extended Visibility'); + } + + // Check blocking + if (reply.userId !== me.id) { + const blockExist = await this.blockingsRepository.exists({ + where: { + blockerId: reply.userId, + blockeeId: me.id, + }, + }); + if (blockExist) { + throw new IdentifiableError('075ca298-e6e7-485a-b570-51a128bb5168', 'You have been blocked by the user'); + } + } + } + //#endregion + + //#region channel + let channel: MiChannel | null = null; + if (data.channelId != null) { + channel = await this.channelsRepository.findOneBy({ id: data.channelId, isArchived: false }); + + if (channel == null) { + throw new IdentifiableError('6815399a-6f13-4069-b60d-ed5156249d12', 'No such channel'); + } + } + //#endregion + } + + @bindThis + public async schedule(draft: MiNoteDraft): Promise { + if (!draft.isActuallyScheduled) return; + if (draft.scheduledAt == null) return; + if (draft.scheduledAt.getTime() <= Date.now()) return; + + const delay = draft.scheduledAt.getTime() - Date.now(); + this.queueService.postScheduledNoteQueue.add(draft.id, { + noteDraftId: draft.id, + }, { + delay, + removeOnComplete: { + age: 3600 * 24 * 7, // keep up to 7 days + count: 30, + }, + removeOnFail: { + age: 3600 * 24 * 7, // keep up to 7 days + count: 100, + }, + }); + } + + @bindThis + public async clearSchedule(draftId: MiNoteDraft['id']): Promise { + // TODO: 線形探索なのをどうにかする + const jobs = await this.queueService.postScheduledNoteQueue.getJobs(['delayed', 'waiting', 'active']); + for (const job of jobs) { + if (job.data.noteDraftId === draftId) { + await job.remove(); + } + } + } +} diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index eeade4569b..310ffec7ce 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -202,7 +202,7 @@ export class NotificationService implements OnApplicationShutdown { } // TODO - //const locales = await import('../../../../locales/index.js'); + //const locales = await import('i18n'); // TODO: locale ファイルをクライアント用とサーバー用で分けたい @@ -271,7 +271,7 @@ export class NotificationService implements OnApplicationShutdown { let untilTime = untilId ? this.toXListId(untilId) : null; let notifications: MiNotification[]; - for (;;) { + for (; ;) { let notificationsRes: [id: string, fields: string[]][]; // sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照 diff --git a/packages/backend/src/core/PageService.ts b/packages/backend/src/core/PageService.ts new file mode 100644 index 0000000000..4abeb30fce --- /dev/null +++ b/packages/backend/src/core/PageService.ts @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { + type NotesRepository, + MiPage, + type PagesRepository, + MiDriveFile, + type UsersRepository, + MiNote, +} from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; +import type { MiUser } from '@/models/User.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export interface PageBody { + title: string; + name: string; + summary: string | null; + content: Array>; + variables: Array>; + script: string; + eyeCatchingImage?: MiDriveFile | null; + font: 'serif' | 'sans-serif'; + alignCenter: boolean; + hideTitleWhenPinned: boolean; +} + +@Injectable() +export class PageService { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private roleService: RoleService, + private moderationLogService: ModerationLogService, + private idService: IdService, + ) { + } + + @bindThis + public async create( + me: MiUser, + body: PageBody, + ): Promise { + await this.pagesRepository.findBy({ + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('1a79e38e-3d83-4423-845b-a9d83ff93b61'); + } + }); + + const page = await this.pagesRepository.insertOne(new MiPage({ + id: this.idService.gen(), + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary, + content: body.content, + variables: body.variables, + script: body.script, + eyeCatchingImageId: body.eyeCatchingImage ? body.eyeCatchingImage.id : null, + userId: me.id, + visibility: 'public', + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + })); + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await this.notesRepository.increment({ id: In(referencedNotes) }, 'pageCount', 1); + } + + return page; + } + + @bindThis + public async update( + me: MiUser, + pageId: MiPage['id'], + body: Partial, + ): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'for_no_key_update' }, + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + if (page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + if (body.name != null) { + await transaction.findBy(MiPage, { + id: Not(pageId), + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('d05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4'); + } + }); + } + + await transaction.update(MiPage, page.id, { + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary === undefined ? page.summary : body.summary, + content: body.content, + variables: body.variables, + script: body.script, + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + eyeCatchingImageId: body.eyeCatchingImage === undefined ? undefined : (body.eyeCatchingImage?.id ?? null), + }); + + console.log('page.content', page.content); + + if (body.content != null) { + const beforeReferencedNotes = this.collectReferencedNotes(page.content); + const afterReferencedNotes = this.collectReferencedNotes(body.content); + + const removedNotes = beforeReferencedNotes.filter(noteId => !afterReferencedNotes.includes(noteId)); + const addedNotes = afterReferencedNotes.filter(noteId => !beforeReferencedNotes.includes(noteId)); + + if (removedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(removedNotes) }, 'pageCount', 1); + } + if (addedNotes.length > 0) { + await transaction.increment(MiNote, { id: In(addedNotes) }, 'pageCount', 1); + } + } + }); + } + + @bindThis + public async delete(me: MiUser, pageId: MiPage['id']): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'pessimistic_write' }, // same lock level as DELETE + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + + if (!await this.roleService.isModerator(me) && page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + await transaction.delete(MiPage, page.id); + + if (page.userId !== me.id) { + const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); + this.moderationLogService.log(me, 'deletePage', { + pageId: page.id, + pageUserId: page.userId, + pageUserUsername: user.username, + page, + }); + } + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(referencedNotes) }, 'pageCount', 1); + } + }); + } + + collectReferencedNotes(content: MiPage['content']): string[] { + const referencingNotes = new Set(); + const recursiveCollect = (content: unknown[]) => { + for (const contentElement of content) { + if (typeof contentElement === 'object' + && contentElement !== null + && 'type' in contentElement) { + if (contentElement.type === 'note' + && 'note' in contentElement + && typeof contentElement.note === 'string') { + referencingNotes.add(contentElement.note); + } + if (contentElement.type === 'section' + && 'children' in contentElement + && Array.isArray(contentElement.children)) { + recursiveCollect(contentElement.children); + } + } + } + }; + recursiveCollect(content); + return [...referencingNotes]; + } +} diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 412ab33b3f..49f93ad108 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets, ObjectLiteral } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiUser } from '@/models/User.js'; -import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js'; +import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository, MiMeta } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { SelectQueryBuilder } from 'typeorm'; @@ -36,40 +36,92 @@ export class QueryService { @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, + @Inject(DI.meta) + private meta: MiMeta, + private idService: IdService, ) { } - public makePaginationQuery(q: SelectQueryBuilder, sinceId?: string | null, untilId?: string | null, sinceDate?: number | null, untilDate?: number | null): SelectQueryBuilder { + public makePaginationQuery( + q: SelectQueryBuilder, + sinceId?: string | null, + untilId?: string | null, + sinceDate?: number | null, + untilDate?: number | null, + targetColumn = 'id', + ): SelectQueryBuilder { if (sinceId && untilId) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: sinceId }); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceId) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); - q.orderBy(`${q.alias}.id`, 'ASC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: sinceId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'ASC'); } else if (untilId) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceDate && untilDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: this.idService.gen(untilDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); - q.orderBy(`${q.alias}.id`, 'ASC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'ASC'); } else if (untilDate) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: this.idService.gen(untilDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else { - q.orderBy(`${q.alias}.id`, 'DESC'); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } return q; } + /** + * ミュートやブロックのようにすべてのタイムラインで共通に使用するフィルターを定義します。 + * + * 特別な事情がない限り、各タイムラインはこの関数を呼び出してフィルターを適用してください。 + * + * Notes for future maintainers: + * 1) この関数で生成するクエリと同等の処理が FanoutTimelineEndpointService にあります。 + * この関数を変更した場合、FanoutTimelineEndpointService の方も変更する必要があります。 + * 2) 以下のエンドポイントでは特別な事情があるため queryService のそれぞれの関数を呼び出しています。 + * この関数を変更した場合、以下のエンドポイントの方も変更する必要があることがあります。 + * - packages/backend/src/server/api/endpoints/clips/notes.ts + */ + @bindThis + public generateBaseNoteFilteringQuery( + query: SelectQueryBuilder, + me: { id: MiUser['id'] } | null, + { + excludeUserFromMute, + excludeAuthor, + }: { + excludeUserFromMute?: MiUser['id'], + excludeAuthor?: boolean, + } = {}, + ): void { + this.generateBlockedHostQueryForNote(query, excludeAuthor); + this.generateSuspendedUserQueryForNote(query, excludeAuthor); + if (me) { + this.generateMutedUserQueryForNotes(query, me, { excludeUserFromMute }); + this.generateBlockedUserQueryForNotes(query, me); + this.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote', excludeUserFromMute }); + this.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); + } + } + // ここでいうBlockedは被Blockedの意 @bindThis - public generateBlockedUserQueryForNotes(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { + public generateBlockedUserQueryForNotes( + q: SelectQueryBuilder, + me: { id: MiUser['id'] }, + { + noteColumn = 'note', + }: { + noteColumn?: string, + } = {}, + ): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockerId') .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); @@ -78,16 +130,20 @@ export class QueryService { // 投稿の返信先の作者にブロックされていない かつ // 投稿の引用元の作者にブロックされていない q - .andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`) .andWhere(new Brackets(qb => { qb - .where('note.replyUserId IS NULL') - .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`); + .where(`${noteColumn}.userId IS NULL`) + .orWhere(`${noteColumn}.userId NOT IN (${ blockingQuery.getQuery() })`); })) .andWhere(new Brackets(qb => { qb - .where('note.renoteUserId IS NULL') - .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); + .where(`${noteColumn}.replyUserId IS NULL`) + .orWhere(`${noteColumn}.replyUserId NOT IN (${ blockingQuery.getQuery() })`); + })) + .andWhere(new Brackets(qb => { + qb + .where(`${noteColumn}.renoteUserId IS NULL`) + .orWhere(`${noteColumn}.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); })); q.setParameters(blockingQuery.getParameters()); @@ -127,13 +183,23 @@ export class QueryService { } @bindThis - public generateMutedUserQueryForNotes(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void { + public generateMutedUserQueryForNotes( + q: SelectQueryBuilder, + me: { id: MiUser['id'] }, + { + excludeUserFromMute, + noteColumn = 'note', + }: { + excludeUserFromMute?: MiUser['id'], + noteColumn?: string, + } = {}, + ): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); - if (exclude) { - mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id }); + if (excludeUserFromMute) { + mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: excludeUserFromMute }); } const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile') @@ -144,32 +210,36 @@ export class QueryService { // 投稿の返信先の作者をミュートしていない かつ // 投稿の引用元の作者をミュートしていない q - .andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`) .andWhere(new Brackets(qb => { qb - .where('note.replyUserId IS NULL') - .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`); + .where(`${noteColumn}.userId IS NULL`) + .orWhere(`${noteColumn}.userId NOT IN (${ mutingQuery.getQuery() })`); })) .andWhere(new Brackets(qb => { qb - .where('note.renoteUserId IS NULL') - .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); + .where(`${noteColumn}.replyUserId IS NULL`) + .orWhere(`${noteColumn}.replyUserId NOT IN (${ mutingQuery.getQuery() })`); + })) + .andWhere(new Brackets(qb => { + qb + .where(`${noteColumn}.renoteUserId IS NULL`) + .orWhere(`${noteColumn}.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); })) // mute instances .andWhere(new Brackets(qb => { qb - .andWhere('note.userHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`); + .andWhere(`${noteColumn}.userHost IS NULL`) + .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.userHost)`); })) .andWhere(new Brackets(qb => { qb - .where('note.replyUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`); + .where(`${noteColumn}.replyUserHost IS NULL`) + .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.replyUserHost)`); })) .andWhere(new Brackets(qb => { qb - .where('note.renoteUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`); + .where(`${noteColumn}.renoteUserHost IS NULL`) + .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? ${noteColumn}.renoteUserHost)`); })); q.setParameters(mutingQuery.getParameters()); @@ -251,4 +321,59 @@ export class QueryService { q.setParameters(mutingQuery.getParameters()); } + + @bindThis + public generateBlockedHostQueryForNote(q: SelectQueryBuilder, excludeAuthor?: boolean): void { + let nonBlockedHostQuery: (part: string) => string; + if (this.meta.blockedHosts.length === 0) { + nonBlockedHostQuery = () => '1=1'; + } else { + nonBlockedHostQuery = (match: string) => `${match} NOT ILIKE ALL(ARRAY[:...blocked])`; + q.setParameters({ blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }); + } + + if (excludeAuthor) { + const instanceSuspension = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) // no corresponding user + .orWhere(`note.userId = note.${user}Id`) + .orWhere(`note.${user}Host IS NULL`) // local + .orWhere(nonBlockedHostQuery(`note.${user}Host`))); + + q + .andWhere(instanceSuspension('replyUser')) + .andWhere(instanceSuspension('renoteUser')); + } else { + const instanceSuspension = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) // no corresponding user + .orWhere(`note.${user}Host IS NULL`) // local + .orWhere(nonBlockedHostQuery(`note.${user}Host`))); + + q + .andWhere(instanceSuspension('user')) + .andWhere(instanceSuspension('replyUser')) + .andWhere(instanceSuspension('renoteUser')); + } + } + + // Requirements: user replyUser renoteUser must be joined + @bindThis + public generateSuspendedUserQueryForNote(q: SelectQueryBuilder, excludeAuthor?: boolean): void { + if (excludeAuthor) { + const brakets = (user: string) => new Brackets(qb => qb + .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮 + .orWhere(`user.id = ${user}.id`) + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } else { + const brakets = (user: string) => new Brackets(qb => qb + .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮 + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere('user.isSuspended = FALSE') + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } + } } diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts index b10b8e5899..ecd96261e0 100644 --- a/packages/backend/src/core/QueueModule.ts +++ b/packages/backend/src/core/QueueModule.ts @@ -16,11 +16,13 @@ import { RelationshipJobData, UserWebhookDeliverJobData, SystemWebhookDeliverJobData, + PostScheduledNoteJobData, } from '../queue/types.js'; import type { Provider } from '@nestjs/common'; export type SystemQueue = Bull.Queue>; export type EndedPollNotificationQueue = Bull.Queue; +export type PostScheduledNoteQueue = Bull.Queue; export type DeliverQueue = Bull.Queue; export type InboxQueue = Bull.Queue; export type DbQueue = Bull.Queue; @@ -41,6 +43,12 @@ const $endedPollNotification: Provider = { inject: [DI.config], }; +const $postScheduledNote: Provider = { + provide: 'queue:postScheduledNote', + useFactory: (config: Config) => new Bull.Queue(QUEUE.POST_SCHEDULED_NOTE, baseQueueOptions(config, QUEUE.POST_SCHEDULED_NOTE)), + inject: [DI.config], +}; + const $deliver: Provider = { provide: 'queue:deliver', useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)), @@ -89,6 +97,7 @@ const $systemWebhookDeliver: Provider = { providers: [ $system, $endedPollNotification, + $postScheduledNote, $deliver, $inbox, $db, @@ -100,6 +109,7 @@ const $systemWebhookDeliver: Provider = { exports: [ $system, $endedPollNotification, + $postScheduledNote, $deliver, $inbox, $db, @@ -113,6 +123,7 @@ export class QueueModule implements OnApplicationShutdown { constructor( @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:postScheduledNote') public postScheduledNoteQueue: PostScheduledNoteQueue, @Inject('queue:deliver') public deliverQueue: DeliverQueue, @Inject('queue:inbox') public inboxQueue: InboxQueue, @Inject('queue:db') public dbQueue: DbQueue, @@ -129,6 +140,7 @@ export class QueueModule implements OnApplicationShutdown { await Promise.all([ this.systemQueue.close(), this.endedPollNotificationQueue.close(), + this.postScheduledNoteQueue.close(), this.deliverQueue.close(), this.inboxQueue.close(), this.dbQueue.close(), diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index a1e806816b..8ef235b238 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -6,7 +6,6 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { MetricsTime, type JobType } from 'bullmq'; -import { parse as parseRedisInfo } from 'redis-info'; import type { IActivity } from '@/core/activitypub/type.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js'; @@ -17,6 +16,7 @@ import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js'; +import type { Packed } from '@/misc/json-schema.js'; import { type UserWebhookPayload } from './UserWebhookService.js'; import type { DbJobData, @@ -30,6 +30,7 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, + PostScheduledNoteQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, @@ -43,6 +44,7 @@ import type * as Bull from 'bullmq'; export const QUEUE_TYPES = [ 'system', 'endedPollNotification', + 'postScheduledNote', 'deliver', 'inbox', 'db', @@ -52,6 +54,50 @@ export const QUEUE_TYPES = [ 'systemWebhookDeliver', ] as const; +const REPEATABLE_SYSTEM_JOB_DEF = [{ + name: 'tickCharts', + pattern: '55 * * * *', +}, { + name: 'resyncCharts', + pattern: '0 0 * * *', +}, { + name: 'cleanCharts', + pattern: '0 0 * * *', +}, { + name: 'aggregateRetention', + pattern: '0 0 * * *', +}, { + name: 'clean', + pattern: '0 0 * * *', +}, { + name: 'checkExpiredMutings', + pattern: '*/5 * * * *', +}, { + name: 'bakeBufferedReactions', + pattern: '0 0 * * *', +}, { + name: 'checkModeratorsActivity', + // 毎時30分に起動 + pattern: '30 * * * *', +}, { + name: 'cleanRemoteNotes', + // 毎日午前4時に起動(最も人の少ない時間帯) + pattern: '0 4 * * *', +}]; + +function parseRedisInfo(infoText: string): Record { + const fields = infoText + .split('\n') + .filter(line => line.length > 0 && !line.startsWith('#')) + .map(line => line.trim().split(':')); + + const result: Record = {}; + for (const [key, value] of fields) { + result[key] = value; + } + return result; +} + @Injectable() export class QueueService { constructor( @@ -60,6 +106,7 @@ export class QueueService { @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:postScheduledNote') public postScheduledNoteQueue: PostScheduledNoteQueue, @Inject('queue:deliver') public deliverQueue: DeliverQueue, @Inject('queue:inbox') public inboxQueue: InboxQueue, @Inject('queue:db') public dbQueue: DbQueue, @@ -68,61 +115,31 @@ export class QueueService { @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { - this.systemQueue.add('tickCharts', { - }, { - repeat: { pattern: '55 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); + for (const def of REPEATABLE_SYSTEM_JOB_DEF) { + this.systemQueue.upsertJobScheduler(def.name, { + pattern: def.pattern, + immediately: false, + }, { + name: def.name, + opts: { + // 期限ではなくcountで設定したいが、ジョブごとではなくキュー全体でカウントされるため、高頻度で実行されるジョブによって低頻度で実行されるジョブのログが消えることになる + removeOnComplete: { + age: 3600 * 24 * 7, // keep up to 7 days + }, + removeOnFail: { + age: 3600 * 24 * 7, // keep up to 7 days + }, + }, + }); + } - this.systemQueue.add('resyncCharts', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('cleanCharts', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('aggregateRetention', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('clean', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('checkExpiredMutings', { - }, { - repeat: { pattern: '*/5 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('bakeBufferedReactions', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('checkModeratorsActivity', { - }, { - // 毎時30分に起動 - repeat: { pattern: '30 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + // 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップ + this.systemQueue.getJobSchedulers().then(schedulers => { + for (const scheduler of schedulers) { + if (!REPEATABLE_SYSTEM_JOB_DEF.some(def => def.name === scheduler.key)) { + this.systemQueue.removeJobScheduler(scheduler.key); + } + } }); } @@ -715,6 +732,7 @@ export class QueueService { switch (type) { case 'system': return this.systemQueue; case 'endedPollNotification': return this.endedPollNotificationQueue; + case 'postScheduledNote': return this.postScheduledNoteQueue; case 'deliver': return this.deliverQueue; case 'inbox': return this.inboxQueue; case 'db': return this.dbQueue; @@ -754,8 +772,8 @@ export class QueueService { @bindThis public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { if (job.finishedOn != null) { await job.retry(); } else { @@ -767,20 +785,20 @@ export class QueueService { @bindThis public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { await job.remove(); } } @bindThis - private packJobData(job: Bull.Job) { + private packJobData(job: Bull.Job): Packed<'QueueJob'> { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const stacktrace = job.stacktrace ? job.stacktrace.filter(Boolean) : []; stacktrace.reverse(); return { - id: job.id, + id: job.id!, name: job.name, data: job.data, opts: job.opts, @@ -801,14 +819,21 @@ export class QueueService { @bindThis public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { return this.packJobData(job); } else { throw new Error(`Job not found: ${jobId}`); } } + @bindThis + public async queueGetJobLogs(queueType: typeof QUEUE_TYPES[number], jobId: string) { + const queue = this.getQueue(queueType); + const result = await queue.getJobLogs(jobId); + return result.logs; + } + @bindThis public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) { const RETURN_LIMIT = 100; diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 601959cc96..2ffee69c21 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -31,6 +31,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { NotificationService } from '@/core/NotificationService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; +// misskey-js の rolePolicies と同期すべし export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; @@ -43,9 +44,11 @@ export type RolePolicies = { canManageCustomEmojis: boolean; canManageAvatarDecorations: boolean; canSearchNotes: boolean; + canSearchUsers: boolean; canUseTranslator: boolean; canHideAds: boolean; driveCapacityMb: number; + maxFileSizeMb: number; alwaysMarkNsfw: boolean; canUpdateBioMedia: boolean; pinLimit: number; @@ -64,6 +67,10 @@ export type RolePolicies = { canImportMuting: boolean; canImportUserLists: boolean; chatAvailability: 'available' | 'readonly' | 'unavailable'; + uploadableFileTypes: string[]; + noteDraftLimit: number; + scheduledNoteLimit: number; + watermarkAvailable: boolean; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -78,9 +85,11 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageCustomEmojis: false, canManageAvatarDecorations: false, canSearchNotes: false, + canSearchUsers: true, canUseTranslator: true, canHideAds: false, driveCapacityMb: 100, + maxFileSizeMb: 30, alwaysMarkNsfw: false, canUpdateBioMedia: true, pinLimit: 5, @@ -93,12 +102,22 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, - canImportAntennas: true, - canImportBlocking: true, - canImportFollowing: true, - canImportMuting: true, - canImportUserLists: true, + canImportAntennas: false, + canImportBlocking: false, + canImportFollowing: false, + canImportMuting: false, + canImportUserLists: false, chatAvailability: 'available', + uploadableFileTypes: [ + 'text/*', + 'application/json', + 'image/*', + 'video/*', + 'audio/*', + ], + noteDraftLimit: 10, + scheduledNoteLimit: 1, + watermarkAvailable: true, }; @Injectable() @@ -295,7 +314,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { default: return false; } - } catch (err) { + } catch (_) { // TODO: log error return false; } @@ -388,9 +407,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), + canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), + maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)), pinLimit: calc('pinLimit', vs => Math.max(...vs)), @@ -409,6 +430,19 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)), canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)), chatAvailability: calc('chatAvailability', aggregateChatAvailability), + uploadableFileTypes: calc('uploadableFileTypes', vs => { + const set = new Set(); + for (const v of vs) { + for (const type of v) { + if (type.trim() === '') continue; + set.add(type.trim()); + } + } + return [...set]; + }), + noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)), + scheduledNoteLimit: calc('scheduledNoteLimit', vs => Math.max(...vs)), + watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)), }; } diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index aa787c93de..87097ada93 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -190,8 +190,7 @@ export class SearchService { return this.searchNoteByMeiliSearch(q, me, opts, pagination); } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const typeCheck: never = this.provider; + const _: never = this.provider; return []; } } @@ -227,15 +226,14 @@ export class SearchService { if (opts.host) { if (opts.host === '.') { - query.andWhere('user.host IS NULL'); + query.andWhere('note.userHost IS NULL'); } else { - query.andWhere('user.host = :host', { host: opts.host }); + query.andWhere('note.userHost = :host', { host: opts.host }); } } this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQueryForNotes(query, me); - if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); return query.limit(pagination.limit).getMany(); } @@ -295,9 +293,20 @@ export class SearchService { this.cacheService.userBlockedCache.fetch(me.id), ]) : [new Set(), new Set()]; - const notes = (await this.notesRepository.findBy({ - id: In(res.hits.map(x => x.id)), - })).filter(note => { + + const query = this.notesRepository.createQueryBuilder('note') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.where('note.id IN (:...noteIds)', { noteIds: res.hits.map(x => x.id) }); + + this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); + + const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; return true; diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 5462cb0b13..a85da62b86 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -93,6 +93,11 @@ export class SignupService { if (isPreserved) { throw new Error('USED_USERNAME'); } + + const hasProhibitedWords = this.utilityService.isKeyWordIncluded(username.toLowerCase(), this.meta.prohibitedWordsForNameOfUser); + if (hasProhibitedWords) { + throw new Error('USED_USERNAME'); + } } const keyPair = await new Promise((res, rej) => diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index f0a8768c8f..61435500b7 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -91,7 +91,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { } @bindThis - public async addMember(target: MiUser, list: MiUserList, me: MiUser) { + public async addMember(target: MiUser, list: MiUserList, me: MiUser, options: { withReplies?: boolean } = {}) { const currentCount = await this.userListMembershipsRepository.countBy({ userListId: list.id, }); @@ -104,6 +104,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { userId: target.id, userListId: list.id, userListUserId: list.userId, + withReplies: options.withReplies ?? false, } as MiUserListMembership); this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id }); diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 7920e58e36..3ecb912a64 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -49,8 +49,8 @@ export class UserSuspendService { }); (async () => { - await this.postSuspend(user).catch(e => {}); - await this.unFollowAll(user).catch(e => {}); + await this.postSuspend(user).catch(_ => {}); + await this.unFollowAll(user).catch(_ => {}); })(); } @@ -67,7 +67,7 @@ export class UserSuspendService { }); (async () => { - await this.postUnsuspend(user).catch(e => {}); + await this.postUnsuspend(user).catch(_ => {}); })(); } diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 23fb928ac9..e3ceebccae 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -6,10 +6,12 @@ import { URL, domainToASCII } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import RE2 from 're2'; +import semver from 'semver'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { MiMeta } from '@/models/Meta.js'; +import { MiMeta, SoftwareSuspension } from '@/models/Meta.js'; +import { MiInstance } from '@/models/Instance.js'; @Injectable() export class UtilityService { @@ -96,7 +98,7 @@ export class UtilityService { try { // TODO: RE2インスタンスをキャッシュ return new RE2(regexp[1], regexp[2]).test(text); - } catch (err) { + } catch (_) { // This should never happen due to input sanitisation. return false; } @@ -131,6 +133,7 @@ export class UtilityService { @bindThis public isFederationAllowedHost(host: string): boolean { + if (this.isSelfHost(host)) return true; if (this.meta.federation === 'none') return false; if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false; if (this.isBlockedHost(this.meta.blockedHosts, host)) return false; @@ -143,4 +146,20 @@ export class UtilityService { const host = this.extractDbHost(uri); return this.isFederationAllowedHost(host); } + + @bindThis + public isDeliverSuspendedSoftware(software: Pick): SoftwareSuspension | undefined { + if (software.softwareName == null) return undefined; + if (software.softwareVersion == null) { + // software version is null; suspend iff versionRange is * + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && x.versionRange.trim() === '*'); + } else { + const softwareVersion = software.softwareVersion; + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && semver.satisfies(softwareVersion, x.versionRange, { includePrerelease: true })); + } + } } diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 372e1e2ab7..31c8d67c60 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -66,7 +66,6 @@ export class WebAuthnService { userID: isoUint8Array.fromUTF8String(userId), userName: userName, userDisplayName: userDisplayName, - attestationType: 'indirect', excludeCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{ id: key.id, transports: key.transports ?? undefined, diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 9cf985b688..b112912b1b 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -85,6 +85,7 @@ function generateDummyNote(override?: Partial): MiNote { renoteCount: 10, repliesCount: 5, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, @@ -105,6 +106,7 @@ function generateDummyNote(override?: Partial): MiNote { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, ...override, }; } @@ -243,7 +245,6 @@ export class WebhookTestService { case 'reaction': return; default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -326,7 +327,6 @@ export class WebhookTestService { break; } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -411,7 +411,7 @@ export class WebhookTestService { name: user.name, username: user.username, host: user.host, - avatarUrl: user.avatarId == null ? null : user.avatarUrl, + avatarUrl: (user.avatarId == null ? null : user.avatarUrl) ?? '', avatarBlurhash: user.avatarId == null ? null : user.avatarBlurhash, avatarDecorations: user.avatarDecorations.map(it => ({ id: it.id, diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index e88f60b806..ff47ca930d 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -14,8 +15,8 @@ import { NotePiningService } from '@/core/NotePiningService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { acquireApObjectLock } from '@/misc/distributed-lock.js'; import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; import { IdService } from '@/core/IdService.js'; import { StatusError } from '@/misc/status-error.js'; @@ -48,8 +49,8 @@ export class ApInboxService { @Inject(DI.config) private config: Config, - @Inject(DI.meta) - private meta: MiMeta, + @Inject(DI.redis) + private redisClient: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -76,7 +77,6 @@ export class ApInboxService { private userBlockingService: UserBlockingService, private noteCreateService: NoteCreateService, private noteDeleteService: NoteDeleteService, - private appLockService: AppLockService, private apResolverService: ApResolverService, private apDbResolverService: ApDbResolverService, private apLoggerService: ApLoggerService, @@ -95,7 +95,7 @@ export class ApInboxService { if (isCollectionOrOrderedCollection(activity)) { const results = [] as [string, string | void][]; // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const items = toArray(isCollection(activity) ? activity.items : activity.orderedItems); if (items.length >= resolver.getRecursionLimit()) { @@ -221,7 +221,7 @@ export class ApInboxService { this.logger.info(`Accept: ${uri}`); // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(err => { this.logger.error(`Resolution failed: ${err}`); @@ -284,7 +284,7 @@ export class ApInboxService { this.logger.info(`Announce: ${uri}`); // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); if (!activity.object) return 'skip: activity has no object property'; const targetUri = getApId(activity.object); @@ -311,7 +311,7 @@ export class ApInboxService { // アナウンス先が許可されているかチェック if (!this.utilityService.isFederationAllowedUri(uri)) return; - const unlock = await this.appLockService.getApLock(uri); + const unlock = await acquireApObjectLock(this.redisClient, uri); try { // 既に同じURIを持つものが登録されていないかチェック @@ -406,7 +406,7 @@ export class ApInboxService { } // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { this.logger.error(`Resolution failed: ${e}`); @@ -438,7 +438,7 @@ export class ApInboxService { } } - const unlock = await this.appLockService.getApLock(uri); + const unlock = await acquireApObjectLock(this.redisClient, uri); try { const exist = await this.apNoteService.fetchNote(note); @@ -522,7 +522,7 @@ export class ApInboxService { private async deleteNote(actor: MiRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Note: ${uri}`); - const unlock = await this.appLockService.getApLock(uri); + const unlock = await acquireApObjectLock(this.redisClient, uri); try { const note = await this.apDbResolverService.getNoteFromApId(uri); @@ -575,7 +575,7 @@ export class ApInboxService { this.logger.info(`Reject: ${uri}`); // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { this.logger.error(`Resolution failed: ${e}`); @@ -642,7 +642,7 @@ export class ApInboxService { this.logger.info(`Undo: ${uri}`); // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { this.logger.error(`Resolution failed: ${e}`); @@ -774,7 +774,7 @@ export class ApInboxService { this.logger.debug('Update'); // eslint-disable-next-line no-param-reassign - resolver ??= this.apResolverService.createResolver(); + resolver ??= await this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { this.logger.error(`Resolution failed: ${e}`); diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index f4c07e472c..a928ed5ccf 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import * as mfm from 'mfm-js'; -import { MfmService, Appender } from '@/core/MfmService.js'; +import { MfmService } from '@/core/MfmService.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { extractApHashtagObjects } from './models/tag.js'; @@ -25,17 +25,17 @@ export class ApMfmService { } @bindThis - public getNoteHtml(note: Pick, additionalAppender: Appender[] = []) { + public getNoteHtml(note: Pick, extraHtml: string | null = null) { let noMisskeyContent = false; const srcMfm = (note.text ?? ''); const parsed = mfm.parse(srcMfm); - if (!additionalAppender.length && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { + if (extraHtml == null && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { noMisskeyContent = true; } - const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), additionalAppender); + const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), extraHtml); return { content, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 55521d6e3a..8c461b6031 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -19,7 +19,7 @@ import type { MiEmoji } from '@/models/Emoji.js'; import type { MiPoll } from '@/models/Poll.js'; import type { MiPollVote } from '@/models/PollVote.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; -import { MfmService, type Appender } from '@/core/MfmService.js'; +import { MfmService } from '@/core/MfmService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { MiUserKeypair } from '@/models/UserKeypair.js'; @@ -28,6 +28,7 @@ import { bindThis } from '@/decorators.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { IdService } from '@/core/IdService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { escapeHtml } from '@/misc/escape-html.js'; import { JsonLdService } from './JsonLdService.js'; import { ApMfmService } from './ApMfmService.js'; import { CONTEXT } from './misc/contexts.js'; @@ -384,7 +385,7 @@ export class ApRendererService { inReplyTo = null; } - let quote; + let quote: string | undefined; if (note.renoteId) { const renote = await this.notesRepository.findOneBy({ id: note.renoteId }); @@ -430,29 +431,18 @@ export class ApRendererService { poll = await this.pollsRepository.findOneBy({ noteId: note.id }); } - const apAppend: Appender[] = []; + let extraHtml: string | null = null; - if (quote) { + if (quote != null) { // Append quote link as `

RE: ...` - // the claas name `quote-inline` is used in non-misskey clients for styling quote notes. + // the class name `quote-inline` is used in non-misskey clients for styling quote notes. // For compatibility, the span part should be kept as possible. - apAppend.push((doc, body) => { - body.appendChild(doc.createElement('br')); - body.appendChild(doc.createElement('br')); - const span = doc.createElement('span'); - span.className = 'quote-inline'; - span.appendChild(doc.createTextNode('RE: ')); - const link = doc.createElement('a'); - link.setAttribute('href', quote); - link.textContent = quote; - span.appendChild(link); - body.appendChild(span); - }); + extraHtml = `

RE: ${escapeHtml(quote)}`; } const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; - const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend); + const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, extraHtml); const emojis = await this.getEmojis(note.emojis); const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji)); @@ -525,7 +515,7 @@ export class ApRendererService { const restPart = maybeUrl.slice(match[0].length); return `${urlPart}${restPart}`; - } catch (e) { + } catch (_) { return maybeUrl; } }; diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 61d328ccac..d14b82dc92 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -6,7 +6,7 @@ import * as crypto from 'node:crypto'; import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import { Window } from 'happy-dom'; +import * as htmlParser from 'node-html-parser'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { MiUser } from '@/models/User.js'; @@ -215,29 +215,9 @@ export class ApRequestService { _followAlternate === true ) { const html = await res.text(); - const { window, happyDOM } = new Window({ - settings: { - disableJavaScriptEvaluation: true, - disableJavaScriptFileLoading: true, - disableCSSFileLoading: true, - disableComputedStyleRendering: true, - handleDisabledFileLoadingAsSuccess: true, - navigation: { - disableMainFrameNavigation: true, - disableChildFrameNavigation: true, - disableChildPageNavigation: true, - disableFallbackToSetURL: true, - }, - timer: { - maxTimeout: 0, - maxIntervalTime: 0, - maxIntervalIterations: 0, - }, - }, - }); - const document = window.document; + try { - document.documentElement.innerHTML = html; + const document = htmlParser.parse(html); const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); if (alternate) { @@ -246,10 +226,8 @@ export class ApRequestService { return await this.signedGet(href, user, allowSoftfail, false); } } - } catch (e) { + } catch (_) { // something went wrong parsing the HTML, ignore the whole thing - } finally { - happyDOM.close().catch(err => {}); } } //#endregion diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index 2534899ad1..0f51b1ce8d 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -3,10 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; +import type { + FollowRequestsRepository, + MiMeta, + NoteReactionsRepository, + NotesRepository, + PollsRepository, + UsersRepository +} from '@/models/_.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; @@ -16,26 +23,43 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { SystemAccountService } from '@/core/SystemAccountService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { ICollection, IObject, IOrderedCollection } from './type.js'; import { isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; import { FetchAllowSoftFailMask } from './misc/check-against-url.js'; -import type { IObject, ICollection, IOrderedCollection } from './type.js'; +import { ModuleRef } from '@nestjs/core'; +@Injectable({ scope: Scope.TRANSIENT }) export class Resolver { private history: Set; private user?: MiLocalUser; private logger: Logger; + private recursionLimit = 256; constructor( + @Inject(DI.config) private config: Config, + + @Inject(DI.meta) private meta: MiMeta, + + @Inject(DI.usersRepository) private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) private pollsRepository: PollsRepository, + + @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, + + @Inject(DI.followRequestsRepository) private followRequestsRepository: FollowRequestsRepository, + private utilityService: UtilityService, private systemAccountService: SystemAccountService, private apRequestService: ApRequestService, @@ -43,7 +67,6 @@ export class Resolver { private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, private loggerService: LoggerService, - private recursionLimit = 256, ) { this.history = new Set(); this.logger = this.loggerService.getLogger('ap-resolve'); @@ -104,7 +127,7 @@ export class Resolver { throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked'); } - if (this.config.signToActivityPubGet && !this.user) { + if (this.meta.signToActivityPubGet && !this.user) { this.user = await this.systemAccountService.fetch('actor'); } @@ -180,54 +203,12 @@ export class Resolver { @Injectable() export class ApResolverService { constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.meta) - private meta: MiMeta, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - @Inject(DI.followRequestsRepository) - private followRequestsRepository: FollowRequestsRepository, - - private utilityService: UtilityService, - private systemAccountService: SystemAccountService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - private loggerService: LoggerService, + private moduleRef: ModuleRef, ) { } @bindThis - public createResolver(): Resolver { - return new Resolver( - this.config, - this.meta, - this.usersRepository, - this.notesRepository, - this.pollsRepository, - this.noteReactionsRepository, - this.followRequestsRepository, - this.utilityService, - this.systemAccountService, - this.apRequestService, - this.httpRequestService, - this.apRendererService, - this.apDbResolverService, - this.loggerService, - ); + public async createResolver(): Promise { + return await this.moduleRef.create(Resolver); } } diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index e7ece87b01..0496774c19 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -46,7 +46,7 @@ export class ApImageService { throw new Error('actor has been suspended'); } - const image = await this.apResolverService.createResolver().resolve(value); + const image = await (await this.apResolverService.createResolver()).resolve(value); if (!isDocument(image)) return null; diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 8abacd293f..1fc5728c98 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -5,14 +5,15 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { PollsRepository, EmojisRepository, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { MiRemoteUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; +import { acquireApObjectLock } from '@/misc/distributed-lock.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; import type { MiEmoji } from '@/models/Emoji.js'; -import { AppLockService } from '@/core/AppLockService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import type Logger from '@/logger.js'; @@ -48,6 +49,9 @@ export class ApNoteService { @Inject(DI.meta) private meta: MiMeta, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.pollsRepository) private pollsRepository: PollsRepository, @@ -67,7 +71,6 @@ export class ApNoteService { private apMentionService: ApMentionService, private apImageService: ApImageService, private apQuestionService: ApQuestionService, - private appLockService: AppLockService, private pollService: PollService, private noteCreateService: NoteCreateService, private apDbResolverService: ApDbResolverService, @@ -125,7 +128,7 @@ export class ApNoteService { @bindThis public async createNote(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver, silent = false): Promise { // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); const object = await resolver.resolve(value); @@ -354,7 +357,7 @@ export class ApNoteService { throw new StatusError('blocked host', 451); } - const unlock = await this.appLockService.getApLock(uri); + const unlock = await acquireApObjectLock(this.redisClient, uri); try { //#region このサーバーに既に登録されていたらそれを返す diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index e52078ed0f..ebe8e9c964 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -310,7 +310,7 @@ export class ApPersonService implements OnModuleInit { } // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); const object = await resolver.resolve(uri); if (object.id == null) throw new Error('invalid object.id: ' + object.id); @@ -500,7 +500,7 @@ export class ApPersonService implements OnModuleInit { //#endregion // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); const object = hint ?? await resolver.resolve(uri); @@ -678,7 +678,7 @@ export class ApPersonService implements OnModuleInit { // リモートサーバーからフェッチしてきて登録 // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); return await this.createPerson(uri, resolver); } @@ -707,7 +707,7 @@ export class ApPersonService implements OnModuleInit { this.logger.info(`Updating the featured: ${user.uri}`); - const _resolver = resolver ?? this.apResolverService.createResolver(); + const _resolver = resolver ?? await this.apResolverService.createResolver(); // Resolve to (Ordered)Collection Object const collection = await _resolver.resolveCollection(user.featured); diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index a2cdaf02ca..8ac2f21e26 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -45,7 +45,7 @@ export class ApQuestionService { @bindThis public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); const question = await resolver.resolve(source); if (!isQuestion(question)) throw new Error('invalid type'); @@ -91,7 +91,7 @@ export class ApQuestionService { // resolve new Question object // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + if (resolver == null) resolver = await this.apResolverService.createResolver(); const question = await resolver.resolve(value); this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 05905f3782..7b9840af87 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -5,11 +5,12 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; @@ -28,11 +29,13 @@ export default class ActiveUsersChart extends Chart { // eslint-d @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private chartLoggerService: ChartLoggerService, private idService: IdService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 04e771a95b..ed790de7b5 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -5,9 +5,10 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/ap-request.js'; @@ -22,10 +23,12 @@ export default class ApRequestChart extends Chart { // eslint-dis @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index 613e074a9f..782873809a 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/drive.js'; @@ -23,10 +24,12 @@ export default class DriveChart extends Chart { // eslint-disable @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index c9b43cc66d..b7a7f640b8 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { FollowingsRepository, InstancesRepository, MiMeta } from '@/models/_.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/federation.js'; @@ -26,16 +27,18 @@ export default class FederationChart extends Chart { // eslint-di @Inject(DI.meta) private meta: MiMeta, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, - private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 97f3bc6f2b..b1657e0a0b 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -5,13 +5,14 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiNote } from '@/models/Note.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/instance.js'; @@ -26,6 +27,9 @@ export default class InstanceChart extends Chart { // eslint-disa @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -39,10 +43,9 @@ export default class InstanceChart extends Chart { // eslint-disa private followingsRepository: FollowingsRepository, private utilityService: UtilityService, - private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index f763b5fffa..aa64e2329a 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -5,11 +5,12 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { NotesRepository } from '@/models/_.js'; import type { MiNote } from '@/models/Note.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/notes.js'; @@ -24,13 +25,15 @@ export default class NotesChart extends Chart { // eslint-disable @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, - private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index 404964d8b7..f7e92aecea 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -5,12 +5,13 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { DriveFilesRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-drive.js'; @@ -25,14 +26,16 @@ export default class PerUserDriveChart extends Chart { // eslint- @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private appLockService: AppLockService, private driveFileEntityService: DriveFileEntityService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 588ac638de..ea431a5131 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -5,12 +5,13 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FollowingsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; @@ -25,14 +26,16 @@ export default class PerUserFollowingChart extends Chart { // esl @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - private appLockService: AppLockService, private userEntityService: UserEntityService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index e4900772bb..824d60042d 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -5,12 +5,13 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import type { NotesRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; @@ -25,13 +26,15 @@ export default class PerUserNotesChart extends Chart { // eslint- @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, - private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-pv.ts b/packages/backend/src/core/chart/charts/per-user-pv.ts index 31708fefa8..b3e1b2cea1 100644 --- a/packages/backend/src/core/chart/charts/per-user-pv.ts +++ b/packages/backend/src/core/chart/charts/per-user-pv.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-pv.js'; @@ -23,10 +24,12 @@ export default class PerUserPvChart extends Chart { // eslint-dis @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index c29c4d2870..7bc1d9e7fa 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -5,12 +5,13 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-reactions.js'; @@ -25,11 +26,13 @@ export default class PerUserReactionsChart extends Chart { // esl @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private userEntityService: UserEntityService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index 7a2844f4ed..8dd1a5d996 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-grouped.js'; import type { KVs } from '../core.js'; @@ -24,10 +25,12 @@ export default class TestGroupedChart extends Chart { // eslint-d @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + logger: Logger, ) { - super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema, true); + super(db, (k) => acquireChartInsertLock(redisClient, k), logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index b8d0556c9f..23b8649cce 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-intersection.js'; import type { KVs } from '../core.js'; @@ -22,10 +23,12 @@ export default class TestIntersectionChart extends Chart { // esl @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + logger: Logger, ) { - super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index f94e008059..b84dd419ba 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-unique.js'; import type { KVs } from '../core.js'; @@ -22,10 +23,12 @@ export default class TestUniqueChart extends Chart { // eslint-di @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + logger: Logger, ) { - super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index a90dc8f99b..0e95ce9239 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -5,10 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { AppLockService } from '@/core/AppLockService.js'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { name, schema } from './entities/test.js'; import type { KVs } from '../core.js'; @@ -24,10 +25,12 @@ export default class TestChart extends Chart { // eslint-disable- @Inject(DI.db) private db: DataSource, - private appLockService: AppLockService, + @Inject(DI.redis) + private redisClient: Redis.Redis, + logger: Logger, ) { - super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index d148fc629b..4471c1df23 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -5,12 +5,13 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; -import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UsersRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; +import { acquireChartInsertLock } from '@/misc/distributed-lock.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; @@ -25,14 +26,16 @@ export default class UsersChart extends Chart { // eslint-disable @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, - private appLockService: AppLockService, private userEntityService: UserEntityService, private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); + super(db, (k) => acquireChartInsertLock(redisClient, k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 1ba7ca8e57..e26cddd281 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,8 @@ 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, + bannerId: channel.bannerId, pinnedNoteIds: channel.pinnedNoteIds, color: channel.color, isArchived: channel.isArchived, @@ -90,6 +123,7 @@ export class ChannelEntityService { ...(me ? { isFollowing, isFavorited, + isMuting, hasUnreadNote: false, // 後方互換性のため } : {}), @@ -98,5 +132,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/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts index da112d5444..f69a484398 100644 --- a/packages/backend/src/core/entities/ChatEntityService.ts +++ b/packages/backend/src/core/entities/ChatEntityService.ts @@ -54,12 +54,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -76,7 +77,7 @@ export class ChatEntityService { toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } @@ -108,6 +109,7 @@ export class ChatEntityService { } } + // TODO: packedUsersに削除されたユーザーもnullとして含める const [packedUsers, packedFiles, packedRooms] = await Promise.all([ this.userEntityService.packMany(users, me) .then(users => new Map(users.map(u => [u.id, u]))), @@ -136,7 +138,7 @@ export class ChatEntityService { const reactions: { reaction: string; }[] = []; for (const record of message.reactions) { - const [userId, reaction] = record.split('/'); + const [, reaction] = record.split('/'); reactions.push({ reaction, }); @@ -183,12 +185,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -202,7 +205,7 @@ export class ChatEntityService { toRoomId: message.toRoomId!, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } @@ -238,13 +241,15 @@ export class ChatEntityService { options?: { _hint_?: { packedOwners: Map>; - memberships?: Map; + myMemberships?: Map; + myInvitations?: Map; }; }, ): Promise> { const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src }); - const membership = me && me.id !== room.ownerId ? (options?._hint_?.memberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null; + const membership = me && me.id !== room.ownerId ? (options?._hint_?.myMemberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null; + const invitation = me && me.id !== room.ownerId ? (options?._hint_?.myInvitations?.get(room.id) ?? await this.chatRoomInvitationsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null; return { id: room.id, @@ -254,6 +259,7 @@ export class ChatEntityService { ownerId: room.ownerId, owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me), isMuted: membership != null ? membership.isMuted : false, + invitationExists: invitation != null, }; } @@ -278,7 +284,7 @@ export class ChatEntityService { const owners = _rooms.map(x => x.owner ?? x.ownerId); - const [packedOwners, memberships] = await Promise.all([ + const [packedOwners, myMemberships, myInvitations] = await Promise.all([ this.userEntityService.packMany(owners, me) .then(users => new Map(users.map(u => [u.id, u]))), this.chatRoomMembershipsRepository.find({ @@ -287,9 +293,15 @@ export class ChatEntityService { userId: me.id, }, }).then(memberships => new Map(_rooms.map(r => [r.id, memberships.find(m => m.roomId === r.id)]))), + this.chatRoomInvitationsRepository.find({ + where: { + roomId: In(_rooms.map(x => x.id)), + userId: me.id, + }, + }).then(invitations => new Map(_rooms.map(r => [r.id, invitations.find(i => i.roomId === r.id)]))), ]); - return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, memberships } }))); + return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, myMemberships, myInvitations } }))); } @bindThis diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index c485555f90..1865d494c4 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -6,7 +6,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; @@ -17,6 +17,7 @@ import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { IdService } from '@/core/IdService.js'; +import { uniqueByKey } from '@/misc/unique-by-key.js'; import { UtilityService } from '../UtilityService.js'; import { VideoProcessingService } from '../VideoProcessingService.js'; import { UserEntityService } from './UserEntityService.js'; @@ -34,6 +35,9 @@ export class DriveFileEntityService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, @@ -95,7 +99,7 @@ export class DriveFileEntityService { return this.getProxiedUrl(file.uri, 'static'); } - if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) { + if (file.uri != null && file.isLink && this.meta.proxyRemoteFiles) { // リモートかつ期限切れはローカルプロキシを試みる // 従来は/files/${thumbnailAccessKey}にアクセスしていたが、 // /filesはメディアプロキシにリダイレクトするようにしたため直接メディアプロキシを指定する @@ -115,7 +119,7 @@ export class DriveFileEntityService { } // リモートかつ期限切れはローカルプロキシを試みる - if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) { + if (file.uri != null && file.isLink && this.meta.proxyRemoteFiles) { const key = file.webpublicAccessKey; if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 @@ -223,6 +227,7 @@ export class DriveFileEntityService { options?: PackOptions, hint?: { packedUser?: Packed<'UserLite'> + packedFolder?: Packed<'DriveFolder'> }, ): Promise | null> { const opts = Object.assign({ @@ -247,9 +252,9 @@ export class DriveFileEntityService { thumbnailUrl: this.getThumbnailUrl(file), comment: file.comment, folderId: file.folderId, - folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { + folder: opts.detail && file.folderId ? (hint?.packedFolder ?? this.driveFolderEntityService.pack(file.folderId, { detail: true, - }) : null, + })) : null, userId: file.userId, user: (opts.withUser && file.userId) ? hint?.packedUser ?? this.userEntityService.pack(file.userId) : null, }); @@ -260,10 +265,41 @@ export class DriveFileEntityService { files: MiDriveFile[], options?: PackOptions, ): Promise[]> { - const _user = files.map(({ user, userId }) => user ?? userId).filter(x => x != null); - const _userMap = await this.userEntityService.packMany(_user) - .then(users => new Map(users.map(user => [user.id, user]))); - const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {}))); + // -- ユーザ情報の事前取得 -- + + let userMap: Map> | null = null; + if (options?.withUser) { + const users = files + .map(({ user, userId }) => user ?? userId) + .filter(x => x != null); + + const uniqueUsers = uniqueByKey(users, (user) => typeof user === 'string' ? user : user.id); + const packedUsers = await this.userEntityService.packMany(uniqueUsers); + userMap = new Map(packedUsers.map(user => [user.id, user])); + } + + // -- フォルダ情報の事前取得 -- + + let folderMap: Map> | null = null; + if (options?.detail) { + const folders = files + .map(({ folder, folderId }) => folder ?? folderId) + .filter(x => x != null); + + const uniqueFolders = uniqueByKey(folders, (folder) => typeof folder === 'string' ? folder : folder.id); + const packedFolders = await this.driveFolderEntityService.packMany(uniqueFolders, { detail: true }); + folderMap = new Map(packedFolders.map(folder => [folder.id, folder])); + } + + const items = await Promise.all(files.map(f => this.packNullable( + f, + options, + { + packedUser: f.userId ? userMap?.get(f.userId) : undefined, + packedFolder: f.folderId ? folderMap?.get(f.folderId) : undefined, + }, + ))); + return items.filter(x => x != null); } diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index 299f23ad38..326421e149 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -12,6 +12,9 @@ import type { } from '@/models/Blocking.js'; import type { MiDriveFolder } from '@/models/DriveFolder.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { In } from 'typeorm'; +import { uniqueByKey } from '@/misc/unique-by-key.js'; +import { splitIdAndObjects } from '@/misc/split-id-and-objects.js'; @Injectable() export class DriveFolderEntityService { @@ -32,12 +35,20 @@ export class DriveFolderEntityService { options?: { detail: boolean }, + hint?: { + folderMap?: Map; + foldersCountMap?: Map | null; + filesCountMap?: Map | null; + parentPacker?: (id: string) => Promise>; + }, ): Promise> { const opts = Object.assign({ detail: false, }, options); - const folder = typeof src === 'object' ? src : await this.driveFoldersRepository.findOneByOrFail({ id: src }); + const folder = typeof src === 'object' + ? src + : hint?.folderMap?.get(src) ?? await this.driveFoldersRepository.findOneByOrFail({ id: src }); return await awaitAll({ id: folder.id, @@ -46,20 +57,141 @@ export class DriveFolderEntityService { parentId: folder.parentId, ...(opts.detail ? { - foldersCount: this.driveFoldersRepository.countBy({ - parentId: folder.id, - }), - filesCount: this.driveFilesRepository.countBy({ - folderId: folder.id, - }), + foldersCount: hint?.foldersCountMap?.get(folder.id) + ?? this.driveFoldersRepository.countBy({ + parentId: folder.id, + }), + filesCount: hint?.filesCountMap?.get(folder.id) + ?? this.driveFilesRepository.countBy({ + folderId: folder.id, + }), ...(folder.parentId ? { - parent: this.pack(folder.parentId, { - detail: true, - }), + parent: hint?.parentPacker + ? hint.parentPacker(folder.parentId) + : this.pack(folder.parentId, { detail: true }, hint), } : {}), } : {}), }); } -} + public async packMany( + src: Array, + options?: { + detail: boolean + }, + ): Promise>> { + /** + * 重複を除去しつつ、必要なDriveFolderオブジェクトをすべて取得する + */ + const collectUniqueObjects = async (src: Array) => { + const uniqueSrc = uniqueByKey( + src, + (s) => typeof s === 'string' ? s : s.id, + ); + const { ids, objects } = splitIdAndObjects(uniqueSrc); + + const uniqueObjects = new Map(objects.map(s => [s.id, s])); + const needsFetchIds = ids.filter(id => !uniqueObjects.has(id)); + + if (needsFetchIds.length > 0) { + const fetchedObjects = await this.driveFoldersRepository.find({ + where: { + id: In(needsFetchIds), + }, + }); + for (const obj of fetchedObjects) { + uniqueObjects.set(obj.id, obj); + } + } + + return uniqueObjects; + }; + + /** + * 親フォルダーを再帰的に収集する + */ + const collectAncestors = async (folderMap: Map) => { + for (;;) { + const parentIds = new Set(); + for (const folder of folderMap.values()) { + if (folder.parentId != null && !folderMap.has(folder.parentId)) { + parentIds.add(folder.parentId); + } + } + + if (parentIds.size === 0) break; + + const fetchedParents = await this.driveFoldersRepository.find({ + where: { + id: In([...parentIds]), + }, + }); + + if (fetchedParents.length === 0) break; + + for (const parent of fetchedParents) { + folderMap.set(parent.id, parent); + } + } + }; + + const opts = Object.assign({ + detail: false, + }, options); + + const folderMap = await collectUniqueObjects(src); + + let foldersCountMap: Map | null = null; + let filesCountMap: Map | null = null; + if (opts.detail) { + await collectAncestors(folderMap); + + const ids = [...folderMap.keys()]; + if (ids.length > 0) { + const folderCounts = await this.driveFoldersRepository.createQueryBuilder('folder') + .select('folder.parentId', 'parentId') + .addSelect('COUNT(*)', 'count') + .where('folder.parentId IN (:...ids)', { ids }) + .groupBy('folder.parentId') + .getRawMany<{ parentId: string; count: string }>(); + + const fileCounts = await this.driveFilesRepository.createQueryBuilder('file') + .select('file.folderId', 'folderId') + .addSelect('COUNT(*)', 'count') + .where('file.folderId IN (:...ids)', { ids }) + .groupBy('file.folderId') + .getRawMany<{ folderId: string; count: string }>(); + + foldersCountMap = new Map(folderCounts.map(row => [row.parentId, Number(row.count)])); + filesCountMap = new Map(fileCounts.map(row => [row.folderId, Number(row.count)])); + } else { + foldersCountMap = new Map(); + filesCountMap = new Map(); + } + } + + const packedMap = new Map>>(); + const packFromId = (id: string): Promise> => { + const cached = packedMap.get(id); + if (cached) return cached; + + const folder = folderMap.get(id); + if (!folder) { + throw new Error(`DriveFolder not found: ${id}`); + } + + const packedPromise = this.pack(folder, options, { + folderMap, + foldersCountMap, + filesCountMap, + parentPacker: packFromId, + }); + packedMap.set(id, packedPromise); + + return packedPromise; + }; + + return Promise.all(src.map(s => packFromId(typeof s === 'string' ? s : s.id))); + } +} diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 490d3f2511..309de3b08f 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -41,7 +41,7 @@ export class EmojiEntityService { @bindThis public packSimpleMany( - emojis: any[], + emojis: (MiEmoji['id'] | MiEmoji)[], ) { return Promise.all(emojis.map(x => this.packSimple(x))); } @@ -69,7 +69,7 @@ export class EmojiEntityService { @bindThis public packDetailedMany( - emojis: any[], + emojis: (MiEmoji['id'] | MiEmoji)[], ): Promise[]> { return Promise.all(emojis.map(x => this.packDetailed(x))); } diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 284537b986..3688cfb363 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -31,6 +31,7 @@ export class InstanceEntityService { me?: { id: MiUser['id']; } | null | undefined, ): Promise> { const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; + const softwareSuspended = this.utilityService.isDeliverSuspendedSoftware(instance); return { id: instance.id, @@ -41,8 +42,8 @@ export class InstanceEntityService { followingCount: instance.followingCount, followersCount: instance.followersCount, isNotResponding: instance.isNotResponding, - isSuspended: instance.suspensionState !== 'none', - suspensionState: instance.suspensionState, + isSuspended: instance.suspensionState !== 'none' || Boolean(softwareSuspended), + suspensionState: instance.suspensionState === 'none' && softwareSuspended ? 'softwareSuspended' : instance.suspensionState, isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 02783dc450..8e56ddbc02 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -55,13 +55,13 @@ export class MetaEntityService { if (instance.defaultLightTheme) { try { defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme)); - } catch (e) { + } catch (_) { } } if (instance.defaultDarkTheme) { try { defaultDarkTheme = JSON.stringify(JSON5.parse(instance.defaultDarkTheme)); - } catch (e) { + } catch (_) { } } @@ -109,6 +109,7 @@ export class MetaEntityService { maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, defaultLightTheme, defaultDarkTheme, + clientOptions: instance.clientOptions, ads: ads.map(ad => ({ id: ad.id, url: ad.url, @@ -116,6 +117,7 @@ export class MetaEntityService { ratio: ad.ratio, imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive ? true : undefined, })), notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, diff --git a/packages/backend/src/core/entities/NoteDraftEntityService.ts b/packages/backend/src/core/entities/NoteDraftEntityService.ts new file mode 100644 index 0000000000..71e41a588d --- /dev/null +++ b/packages/backend/src/core/entities/NoteDraftEntityService.ts @@ -0,0 +1,190 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { EntityNotFoundError } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Packed } from '@/misc/json-schema.js'; +import { awaitAll } from '@/misc/prelude/await-all.js'; +import type { MiUser, MiNote, MiNoteDraft } from '@/models/_.js'; +import type { NoteDraftsRepository, ChannelsRepository } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { DebounceLoader } from '@/misc/loader.js'; +import { IdService } from '@/core/IdService.js'; +import type { OnModuleInit } from '@nestjs/common'; +import type { UserEntityService } from './UserEntityService.js'; +import type { DriveFileEntityService } from './DriveFileEntityService.js'; +import type { NoteEntityService } from './NoteEntityService.js'; + +@Injectable() +export class NoteDraftEntityService implements OnModuleInit { + private userEntityService: UserEntityService; + private driveFileEntityService: DriveFileEntityService; + private idService: IdService; + private noteEntityService: NoteEntityService; + private noteDraftLoader = new DebounceLoader(this.findNoteDraftOrFail); + + constructor( + private moduleRef: ModuleRef, + + @Inject(DI.noteDraftsRepository) + private noteDraftsRepository: NoteDraftsRepository, + + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + ) { + } + + onModuleInit() { + this.userEntityService = this.moduleRef.get('UserEntityService'); + this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); + this.idService = this.moduleRef.get('IdService'); + this.noteEntityService = this.moduleRef.get('NoteEntityService'); + } + + @bindThis + public async packAttachedFiles(fileIds: MiNote['fileIds'], packedFiles: Map | null>): Promise[]> { + const missingIds = []; + for (const id of fileIds) { + if (!packedFiles.has(id)) missingIds.push(id); + } + if (missingIds.length) { + const additionalMap = await this.driveFileEntityService.packManyByIdsMap(missingIds); + for (const [k, v] of additionalMap) { + packedFiles.set(k, v); + } + } + return fileIds.map(id => packedFiles.get(id)).filter(x => x != null); + } + + @bindThis + public async pack( + src: MiNoteDraft['id'] | MiNoteDraft, + me?: { id: MiUser['id'] } | null | undefined, + options?: { + detail?: boolean; + skipHide?: boolean; + withReactionAndUserPairCache?: boolean; + _hint_?: { + packedFiles: Map | null>; + packedUsers: Map> + }; + }, + ): Promise> { + const opts = Object.assign({ + detail: true, + }, options); + + const noteDraft = typeof src === 'object' ? src : await this.noteDraftLoader.load(src); + + const text = noteDraft.text; + + const channel = noteDraft.channelId + ? noteDraft.channel + ? noteDraft.channel + : await this.channelsRepository.findOneBy({ id: noteDraft.channelId }) + : null; + + const packedFiles = options?._hint_?.packedFiles; + const packedUsers = options?._hint_?.packedUsers; + + async function nullIfEntityNotFound(promise: Promise): Promise { + try { + return await promise; + } catch (err) { + if (err instanceof EntityNotFoundError) { + return null; + } + throw err; + } + } + + const packed: Packed<'NoteDraft'> = await awaitAll({ + id: noteDraft.id, + createdAt: this.idService.parse(noteDraft.id).date.toISOString(), + scheduledAt: noteDraft.scheduledAt?.getTime() ?? null, + isActuallyScheduled: noteDraft.isActuallyScheduled, + userId: noteDraft.userId, + user: packedUsers?.get(noteDraft.userId) ?? this.userEntityService.pack(noteDraft.user ?? noteDraft.userId, me), + text: text, + cw: noteDraft.cw, + visibility: noteDraft.visibility, + localOnly: noteDraft.localOnly, + reactionAcceptance: noteDraft.reactionAcceptance, + visibleUserIds: noteDraft.visibleUserIds, + hashtag: noteDraft.hashtag, + fileIds: noteDraft.fileIds, + files: packedFiles != null ? this.packAttachedFiles(noteDraft.fileIds, packedFiles) : this.driveFileEntityService.packManyByIds(noteDraft.fileIds), + replyId: noteDraft.replyId, + renoteId: noteDraft.renoteId, + channelId: noteDraft.channelId, + channel: channel ? { + id: channel.id, + name: channel.name, + color: channel.color, + isSensitive: channel.isSensitive, + allowRenoteToExternal: channel.allowRenoteToExternal, + userId: channel.userId, + } : undefined, + poll: noteDraft.hasPoll ? { + choices: noteDraft.pollChoices, + multiple: noteDraft.pollMultiple, + expiresAt: noteDraft.pollExpiresAt?.toISOString(), + expiredAfter: noteDraft.pollExpiredAfter, + } : null, + + ...(opts.detail ? { + reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, { + detail: false, + skipHide: opts.skipHide, + })) : undefined, + + renote: noteDraft.renoteId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.renoteId, me, { + detail: true, + skipHide: opts.skipHide, + })) : undefined, + } : {} ), + }); + + return packed; + } + + @bindThis + public async packMany( + noteDrafts: MiNoteDraft[], + me?: { id: MiUser['id'] } | null | undefined, + options?: { + detail?: boolean; + }, + ) { + if (noteDrafts.length === 0) return []; + + // TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく + const fileIds = noteDrafts.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(x => x != null); + const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map(); + const users = [ + ...noteDrafts.map(({ user, userId }) => user ?? userId), + ]; + const packedUsers = await this.userEntityService.packMany(users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + + return await Promise.all(noteDrafts.map(n => this.pack(n, me, { + ...options, + _hint_: { + packedFiles, + packedUsers, + }, + }))); + } + + @bindThis + private findNoteDraftOrFail(id: string): Promise { + return this.noteDraftsRepository.findOneOrFail({ + where: { id }, + relations: ['user'], + }); + } +} diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 97f1c3d739..e7847ba74e 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; +import { EntityNotFoundError, In } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Packed } from '@/misc/json-schema.js'; @@ -15,6 +15,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos import { bindThis } from '@/decorators.js'; import { DebounceLoader } from '@/misc/loader.js'; import { IdService } from '@/core/IdService.js'; +import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; @@ -46,6 +47,17 @@ function getAppearNoteIds(notes: MiNote[]): Set { return appearNoteIds; } +async function nullIfEntityNotFound(promise: Promise): Promise { + try { + return await promise; + } catch (err) { + if (err instanceof EntityNotFoundError) { + return null; + } + throw err; + } +} + @Injectable() export class NoteEntityService implements OnModuleInit { private userEntityService: UserEntityService; @@ -105,12 +117,7 @@ export class NoteEntityService implements OnModuleInit { private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] { if (packedNote.visibility === 'public' || packedNote.visibility === 'home') { const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; - if ((followersOnlyBefore != null) - && ( - (followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000))) - || (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000)) - ) - ) { + if (shouldHideNoteByTime(followersOnlyBefore, packedNote.createdAt)) { packedNote.visibility = 'followers'; } } @@ -130,12 +137,7 @@ export class NoteEntityService implements OnModuleInit { if (!hide) { const hiddenBefore = packedNote.user.makeNotesHiddenBefore; - if ((hiddenBefore != null) - && ( - (hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000))) - || (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000)) - ) - ) { + if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) { hide = true; } } @@ -429,25 +431,28 @@ export class NoteEntityService implements OnModuleInit { userId: channel.userId, } : undefined, mentions: note.mentions.length > 0 ? note.mentions : undefined, + hasPoll: note.hasPoll || undefined, uri: note.uri ?? undefined, url: note.url ?? undefined, ...(opts.detail ? { clippedCount: note.clippedCount, - reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, { + // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される + reply: (note.replyId && note.reply === null) ? null : note.replyId ? nullIfEntityNotFound(this.pack(note.reply ?? note.replyId, me, { detail: false, skipHide: opts.skipHide, withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, - }) : undefined, + })) : undefined, - renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, { + // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される + renote: (note.renoteId && note.renote === null) ? null : note.renoteId ? nullIfEntityNotFound(this.pack(note.renote ?? note.renoteId, me, { detail: true, skipHide: opts.skipHide, withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, - }) : undefined, + })) : undefined, poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, @@ -590,7 +595,45 @@ export class NoteEntityService implements OnModuleInit { private findNoteOrFail(id: string): Promise { return this.notesRepository.findOneOrFail({ where: { id }, - relations: ['user'], + relations: ['user', 'renote', 'reply'], }); } + + @bindThis + public async fetchDiffs(noteIds: MiNote['id'][]) { + if (noteIds.length === 0) return []; + + const notes = await this.notesRepository.find({ + where: { + id: In(noteIds), + }, + select: { + id: true, + userHost: true, + reactions: true, + reactionAndUserPairCache: true, + }, + }); + + const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null; + + const packings = notes.map(note => { + const bufferedReactions = bufferedReactionsMap?.get(note.id); + //const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); + + const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.deltas ?? {})); + + const reactionEmojiNames = Object.keys(reactions) + .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ + .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); + + return this.customEmojiService.populateEmojis(reactionEmojiNames, note.userHost).then(reactionEmojis => ({ + id: note.id, + reactions, + reactionEmojis, + })); + }); + + return await Promise.all(packings); + } } diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 46ec13704c..fe4926bfe3 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit { public async pack( src: MiNoteReaction['id'] | MiNoteReaction, me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, hints?: { packedUser?: Packed<'UserLite'> }, ): Promise> { - const opts = Object.assign({ - withNote: false, + const _opts = Object.assign({ }, options); const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); @@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit { createdAt: this.idService.parse(reaction.id).date.toISOString(), user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), - } : {}), }; } @@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit { public async packMany( reactions: MiNoteReaction[], me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, ): Promise[]> { const opts = Object.assign({ - withNote: false, }, options); const _users = reactions.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); } + + @bindThis + public async packWithNote( + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + hints?: { + packedUser?: Packed<'UserLite'> + }, + ): Promise> { + const _opts = Object.assign({ + }, options); + + const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); + + return { + id: reaction.id, + createdAt: this.idService.parse(reaction.id).date.toISOString(), + user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), + type: this.reactionService.convertLegacyReaction(reaction.reaction), + note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), + }; + } + + @bindThis + public async packManyWithNote( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + ): Promise[]> { + const opts = Object.assign({ + }, options); + const _users = reactions.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); + } } diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index e91fb9eb51..0e96237d32 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -21,7 +21,18 @@ import type { OnModuleInit } from '@nestjs/common'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; -const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded'] as (typeof groupedNotificationTypes[number])[]); +const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set([ + 'note', + 'mention', + 'reply', + 'renote', + 'renote:grouped', + 'quote', + 'reaction', + 'reaction:grouped', + 'pollEnded', + 'scheduledNotePosted', +] as (typeof groupedNotificationTypes[number])[]); @Injectable() export class NotificationEntityService implements OnModuleInit { diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts index df042e75c1..21099bad3e 100644 --- a/packages/backend/src/core/entities/ReversiGameEntityService.ts +++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts @@ -14,6 +14,10 @@ import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; +function assertBw(bw: string): bw is Packed<'ReversiGameDetailed'>['bw'] { + return ['random', '1', '2'].includes(bw); +} + @Injectable() export class ReversiGameEntityService { constructor( @@ -58,7 +62,7 @@ export class ReversiGameEntityService { surrenderedUserId: game.surrenderedUserId, timeoutUserId: game.timeoutUserId, black: game.black, - bw: game.bw, + bw: assertBw(game.bw) ? game.bw : 'random', isLlotheo: game.isLlotheo, canPutEverywhere: game.canPutEverywhere, loopedBoard: game.loopedBoard, @@ -116,7 +120,7 @@ export class ReversiGameEntityService { surrenderedUserId: game.surrenderedUserId, timeoutUserId: game.timeoutUserId, black: game.black, - bw: game.bw, + bw: assertBw(game.bw) ? game.bw : 'random', isLlotheo: game.isLlotheo, canPutEverywhere: game.canPutEverywhere, loopedBoard: game.loopedBoard, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index d4769d24d4..0f4051e7b8 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -471,8 +471,8 @@ export class UserEntityService implements OnModuleInit { (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; - const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null; - const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null; + const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : undefined; + const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : undefined; const unreadAnnouncements = isMe && isDetailed ? (await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({ createdAt: this.idService.parse(announcement.id).date.toISOString(), @@ -481,6 +481,7 @@ export class UserEntityService implements OnModuleInit { const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null; + // TODO: 例えば avatarUrl: true など間違った型を設定しても型エラーにならないのをどうにかする(ジェネリクス使わない方法で実装するしかなさそう?) const packed = { id: user.id, name: user.name, @@ -511,8 +512,8 @@ export class UserEntityService implements OnModuleInit { } : undefined) : undefined, emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), - // パフォーマンス上の理由でローカルユーザーのみ - badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then((rs) => rs + // パフォーマンス上の理由で、明示的に設定しない場合はローカルユーザーのみ取得 + badgeRoles: (this.meta.showRoleBadgesOfRemoteUsers || user.host == null) ? this.roleService.getUserBadgeRoles(user.id).then((rs) => rs .filter((r) => r.isPublic || iAmModerator) .sort((a, b) => b.displayOrder - a.displayOrder) .map((r) => ({ @@ -719,7 +720,7 @@ export class UserEntityService implements OnModuleInit { me, { ...options, - userProfile: profilesMap.get(u.id), + userProfile: profilesMap?.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes, diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index d229efb123..a972e5861c 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -4,13 +4,12 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import si from 'systeminformation'; import Xev from 'xev'; import * as osUtils from 'os-utils'; import { bindThis } from '@/decorators.js'; -import type { OnApplicationShutdown } from '@nestjs/common'; import { MiMeta } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -97,12 +96,14 @@ function cpuUsage(): Promise { // MEMORY STAT async function mem() { + const si = await import('systeminformation'); const data = await si.mem(); return data; } // NETWORK STAT async function net() { + const si = await import('systeminformation'); const iface = await si.networkInterfaceDefault(); const data = await si.networkStats(iface); return data[0]; @@ -110,5 +111,6 @@ async function net() { // FS STAT async function fs() { + const si = await import('systeminformation'); return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 3d6b9bacc2..0506e884f4 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'), @@ -90,5 +91,6 @@ export const DI = { bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'), reversiGamesRepository: Symbol('reversiGamesRepository'), mahjongGamesRepository: Symbol('mahjongGamesRepository'), + noteDraftsRepository: Symbol('noteDraftsRepository'), //#endregion }; diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index ba44cfa2e6..9957938467 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -11,6 +11,7 @@ const envOption = { verbose: false, withLogTime: false, quiet: false, + forceGc: false, }; for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index c50f2b723c..0d1c7ee46e 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -56,7 +56,7 @@ export async function checkWordMute(note: NoteLike, me: UserLike | null | undefi try { return new RE2(regexp[1], regexp[2]).test(text); - } catch (err) { + } catch (_) { // This should never happen due to input sanitisation. return false; } diff --git a/packages/backend/src/misc/distributed-lock.ts b/packages/backend/src/misc/distributed-lock.ts new file mode 100644 index 0000000000..93bd741f62 --- /dev/null +++ b/packages/backend/src/misc/distributed-lock.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Redis from 'ioredis'; + +export async function acquireDistributedLock( + redis: Redis.Redis, + name: string, + timeout: number, + maxRetries: number, + retryInterval: number, +): Promise<() => Promise> { + const lockKey = `lock:${name}`; + const identifier = Math.random().toString(36).slice(2); + + let retries = 0; + while (retries < maxRetries) { + const result = await redis.set(lockKey, identifier, 'PX', timeout, 'NX'); + if (result === 'OK') { + return async () => { + const currentIdentifier = await redis.get(lockKey); + if (currentIdentifier === identifier) { + await redis.del(lockKey); + } + }; + } + + await new Promise(resolve => setTimeout(resolve, retryInterval)); + retries++; + } + + throw new Error(`Failed to acquire lock ${name}`); +} + +export function acquireApObjectLock( + redis: Redis.Redis, + uri: string, +): Promise<() => Promise> { + return acquireDistributedLock(redis, `ap-object:${uri}`, 30 * 1000, 50, 100); +} + +export function acquireChartInsertLock( + redis: Redis.Redis, + name: string, +): Promise<() => Promise> { + return acquireDistributedLock(redis, `chart-insert:${name}`, 30 * 1000, 50, 500); +} diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts index 6d03b433ba..fabbdc335f 100644 --- a/packages/backend/src/misc/emoji-regex.ts +++ b/packages/backend/src/misc/emoji-regex.ts @@ -4,6 +4,6 @@ */ // taken from @twemoji/parser/dist/lib/regex.js -const twemojiRegex = /(?:\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83e\udef1\ud83c\udffb\u200d\ud83e\udef2\ud83c[\udffc-\udfff]|\ud83e\udef1\ud83c\udffc\u200d\ud83e\udef2\ud83c[\udffb\udffd-\udfff]|\ud83e\udef1\ud83c\udffd\u200d\ud83e\udef2\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\udef1\ud83c\udffe\u200d\ud83e\udef2\ud83c[\udffb-\udffd\udfff]|\ud83e\udef1\ud83c\udfff\u200d\ud83e\udef2\ud83c[\udffb-\udffe]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d\udc8f\ud83c[\udffb-\udfff]|\ud83d\udc91\ud83c[\udffb-\udfff]|\ud83e\udd1d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d\udc8f\udc91]|\ud83e\udd1d)|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd4\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83d\ude36\u200d\ud83c\udf2b\ufe0f|\u2764\ufe0f\u200d\ud83d\udd25|\u2764\ufe0f\u200d\ud83e\ude79|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83d\ude2e\u200d\ud83d\udca8|\ud83d\ude35\u200d\ud83d\udcab|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b|\ud83d\udc26\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|\ud83e\udef0|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd-\uddcf\uddd1-\udddd\udec3-\udec5\udef1-\udef8]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udc8e\udc90\udc92-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udedc-\udedf\udeeb\udeec\udef4-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78-\uddb4\uddb7\uddba\uddbc-\uddcc\uddd0\uddde-\uddff\ude70-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec2\udece-\udedb\udee0-\udee8]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g; +const twemojiRegex = /(?:\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83e\udef1\ud83c\udffb\u200d\ud83e\udef2\ud83c[\udffc-\udfff]|\ud83e\udef1\ud83c\udffc\u200d\ud83e\udef2\ud83c[\udffb\udffd-\udfff]|\ud83e\udef1\ud83c\udffd\u200d\ud83e\udef2\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\udef1\ud83c\udffe\u200d\ud83e\udef2\ud83c[\udffb-\udffd\udfff]|\ud83e\udef1\ud83c\udfff\u200d\ud83e\udef2\ud83c[\udffb-\udffe]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d\udc8f\ud83c[\udffb-\udfff]|\ud83d\udc91\ud83c[\udffb-\udfff]|\ud83e\udd1d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d\udc8f\udc91]|\ud83e\udd1d)|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])(?:\u200d\u27a1\ufe0f)?|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f(?:\u200d\u27a1\ufe0f)?)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd4\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f(?:\u200d\u27a1\ufe0f)?|(?:\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83e\uddd1\u200d\ud83e\uddd1\u200d\ud83e\uddd2\u200d\ud83e\uddd2|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83e\uddd1\u200d\ud83e\uddd1\u200d\ud83e\uddd2|\ud83e\uddd1\u200d\ud83e\uddd2\u200d\ud83e\uddd2|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83d\ude36\u200d\ud83c\udf2b\ufe0f|\u26d3\ufe0f\u200d\ud83d\udca5|\u2764\ufe0f\u200d\ud83d\udd25|\u2764\ufe0f\u200d\ud83e\ude79|\ud83c\udf44\u200d\ud83d\udfeb|\ud83c\udf4b\u200d\ud83d\udfe9|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc26\u200d\ud83d\udd25|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83d\ude2e\u200d\ud83d\udca8|\ud83d\ude35\u200d\ud83d\udcab|\ud83d\ude42\u200d\u2194\ufe0f|\ud83d\ude42\u200d\u2195\ufe0f|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddd1\u200d\ud83e\uddd2|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b|\ud83d\udc26\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|\ud83e\udef0|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c\udfc3|\ud83d\udeb6|\ud83e\uddce)(?:\ud83c[\udffb-\udfff])?(?:\u200d\u27a1\ufe0f)?|(?:\ud83c[\udf85\udfc2\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4\udeb5\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd\uddcf\uddd1-\udddd\udec3-\udec5\udef1-\udef8]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udc8e\udc90\udc92-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udedc-\udedf\udeeb\udeec\udef4-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78-\uddb4\uddb7\uddba\uddbc-\uddcc\uddd0\uddde-\uddff\ude70-\ude7c\ude80-\ude89\ude8f-\udec2\udec6\udece-\udedc\udedf-\udee9]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g; export const emojiRegex = new RegExp(`(${twemojiRegex.source})`); diff --git a/packages/backend/src/misc/escape-html.ts b/packages/backend/src/misc/escape-html.ts new file mode 100644 index 0000000000..819aeeed52 --- /dev/null +++ b/packages/backend/src/misc/escape-html.ts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts index e132fa8f31..571996973b 100644 --- a/packages/backend/src/misc/get-ip-hash.ts +++ b/packages/backend/src/misc/get-ip-hash.ts @@ -12,7 +12,7 @@ export function getIpHash(ip: string): string { // (this means for IPv4 the entire address is used) const prefix = IPCIDR.createAddress(ip).mask(64); return 'ip-' + BigInt('0b' + prefix).toString(36); - } catch (e) { + } catch (_) { const prefix = IPCIDR.createAddress(ip.replace(/:[0-9]+$/, '')).mask(64); return 'ip-' + BigInt('0b' + prefix).toString(36); } diff --git a/packages/backend/src/misc/i18n.ts b/packages/backend/src/misc/i18n.ts index 6cbbdef74c..40067cacf5 100644 --- a/packages/backend/src/misc/i18n.ts +++ b/packages/backend/src/misc/i18n.ts @@ -26,7 +26,7 @@ export class I18n> { } } return str; - } catch (e) { + } catch (_) { console.warn(`missing localization '${key}'`); return key; } 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/misc/is-user-related.ts b/packages/backend/src/misc/is-user-related.ts index 862d6e6a38..6f72082733 100644 --- a/packages/backend/src/misc/is-user-related.ts +++ b/packages/backend/src/misc/is-user-related.ts @@ -3,7 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export function isUserRelated(note: any, userIds: Set, ignoreAuthor = false): boolean { +import type { MiUser } from '@/models/_.js'; + +interface NoteLike { + userId: MiUser['id']; + reply?: NoteLike | null; + renote?: NoteLike | null; + replyUserId?: MiUser['id'] | null; + renoteUserId?: MiUser['id'] | null; +} + +export function isUserRelated(note: NoteLike | null | undefined, userIds: Set, ignoreAuthor = false): boolean { if (!note) { return false; } @@ -12,13 +22,16 @@ export function isUserRelated(note: any, userIds: Set, ignoreAuthor = fa return true; } - if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) { + const replyUserId = note.replyUserId ?? note.reply?.userId; + if (replyUserId != null && replyUserId !== note.userId && userIds.has(replyUserId)) { return true; } - if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) { + const renoteUserId = note.renoteUserId ?? note.renote?.userId; + if (renoteUserId != null && renoteUserId !== note.userId && userIds.has(renoteUserId)) { return true; } return false; } + diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index df69f0304a..a73dc8f949 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js'; import { packedMutingSchema } from '@/models/json-schema/muting.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; -import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; +import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; @@ -31,7 +31,11 @@ import { packedChannelSchema } from '@/models/json-schema/channel.js'; import { packedAntennaSchema } from '@/models/json-schema/antenna.js'; import { packedClipSchema } from '@/models/json-schema/clip.js'; import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js'; -import { packedQueueCountSchema } from '@/models/json-schema/queue.js'; +import { + packedQueueCountSchema, + packedQueueMetricsSchema, + packedQueueJobSchema, +} from '@/models/json-schema/queue.js'; import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js'; import { packedEmojiDetailedAdminSchema, @@ -60,7 +64,9 @@ import { packedMetaDetailedOnlySchema, packedMetaDetailedSchema, packedMetaLiteSchema, + packedMetaClientOptionsSchema, } from '@/models/json-schema/meta.js'; +import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js'; import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js'; import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js'; @@ -68,6 +74,8 @@ import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js'; import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js'; import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js'; import { packedMahjongRoomDetailedSchema } from '@/models/json-schema/mahjong-room.js'; +import { packedAchievementNameSchema, packedAchievementSchema } from '@/models/json-schema/achievement.js'; +import { packedNoteDraftSchema } from '@/models/json-schema/note-draft.js'; export const refs = { UserLite: packedUserLiteSchema, @@ -79,11 +87,15 @@ export const refs = { User: packedUserSchema, UserList: packedUserListSchema, + Achievement: packedAchievementSchema, + AchievementName: packedAchievementNameSchema, Ad: packedAdSchema, Announcement: packedAnnouncementSchema, App: packedAppSchema, Note: packedNoteSchema, + NoteDraft: packedNoteDraftSchema, NoteReaction: packedNoteReactionSchema, + NoteReactionWithNote: packedNoteReactionWithNoteSchema, NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, @@ -98,6 +110,8 @@ export const refs = { PageBlock: packedPageBlockSchema, Channel: packedChannelSchema, QueueCount: packedQueueCountSchema, + QueueMetrics: packedQueueMetricsSchema, + QueueJob: packedQueueJobSchema, Antenna: packedAntennaSchema, Clip: packedClipSchema, FederationInstance: packedFederationInstanceSchema, @@ -123,6 +137,8 @@ export const refs = { MetaLite: packedMetaLiteSchema, MetaDetailedOnly: packedMetaDetailedOnlySchema, MetaDetailed: packedMetaDetailedSchema, + MetaClientOptions: packedMetaClientOptionsSchema, + UserWebhook: packedUserWebhookSchema, SystemWebhook: packedSystemWebhookSchema, AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, ChatMessage: packedChatMessageSchema, @@ -211,7 +227,17 @@ type NullOrUndefined

= // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection // Get intersection from union type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; -type PartialIntersection = Partial>; + +type ArrayToIntersection> = + T extends readonly [infer Head, ...infer Tail] + ? Head extends Schema + ? Tail extends ReadonlyArray + ? Tail extends [] + ? SchemaType + : SchemaType & ArrayToIntersection + : never + : never + : never; // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 // To get union, we use `Foo extends any ? Hoge : never` @@ -229,8 +255,8 @@ type ObjectSchemaTypeDef

= : never : ObjType> : - p['anyOf'] extends ReadonlyArray ? never : // see CONTRIBUTING.md - p['allOf'] extends ReadonlyArray ? UnionToIntersection> : + p['anyOf'] extends ReadonlyArray ? UnionSchemaType : + p['allOf'] extends ReadonlyArray ? ArrayToIntersection : p['additionalProperties'] extends true ? Record : p['additionalProperties'] extends Schema ? p['additionalProperties'] extends infer AdditionalProperties ? @@ -240,8 +266,6 @@ type ObjectSchemaTypeDef

= never : any; -type ObjectSchemaType

= NullOrUndefined>; - export type SchemaTypeDef

= p['type'] extends 'null' ? null : p['type'] extends 'integer' ? number : @@ -270,7 +294,8 @@ export type SchemaTypeDef

= p['items'] extends NonNullable ? SchemaType[] : any[] ) : - p['anyOf'] extends ReadonlyArray ? UnionSchemaType & PartialIntersection> : + p['anyOf'] extends ReadonlyArray ? UnionSchemaType : + p['allOf'] extends ReadonlyArray ? ArrayToIntersection : p['oneOf'] extends ReadonlyArray ? UnionSchemaType : any; diff --git a/packages/backend/src/misc/json-stringify-html-safe.ts b/packages/backend/src/misc/json-stringify-html-safe.ts new file mode 100644 index 0000000000..aac12d57db --- /dev/null +++ b/packages/backend/src/misc/json-stringify-html-safe.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const ESCAPE_LOOKUP = { + '&': '\\u0026', + '>': '\\u003e', + '<': '\\u003c', + '\u2028': '\\u2028', + '\u2029': '\\u2029', +} as Record; + +const ESCAPE_REGEX = /[&><\u2028\u2029]/g; + +export function htmlSafeJsonStringify(obj: any): string { + return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]); +} diff --git a/packages/backend/src/misc/should-hide-note-by-time.ts b/packages/backend/src/misc/should-hide-note-by-time.ts new file mode 100644 index 0000000000..ea1951e66c --- /dev/null +++ b/packages/backend/src/misc/should-hide-note-by-time.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * ノートが指定された時間条件に基づいて非表示対象かどうかを判定する + * @param hiddenBefore 非表示条件(負の値: 作成からの経過秒数、正の値: UNIXタイムスタンプ秒、null: 判定しない) + * @param createdAt ノートの作成日時(ISO 8601形式の文字列 または Date オブジェクト) + * @returns 非表示にすべき場合は true + */ +export function shouldHideNoteByTime(hiddenBefore: number | null | undefined, createdAt: string | Date): boolean { + if (hiddenBefore == null) { + return false; + } + + const createdAtTime = typeof createdAt === 'string' ? new Date(createdAt).getTime() : createdAt.getTime(); + + if (hiddenBefore <= 0) { + // 負の値: 作成からの経過時間(秒)で判定 + const elapsedSeconds = (Date.now() - createdAtTime) / 1000; + const hideAfterSeconds = Math.abs(hiddenBefore); + return elapsedSeconds >= hideAfterSeconds; + } else { + // 正の値: 絶対的なタイムスタンプ(秒)で判定 + const createdAtSeconds = createdAtTime / 1000; + return createdAtSeconds <= hiddenBefore; + } +} diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts index 8ddec35f23..b279eb9546 100644 --- a/packages/backend/src/misc/show-machine-info.ts +++ b/packages/backend/src/misc/show-machine-info.ts @@ -4,15 +4,11 @@ */ import * as os from 'node:os'; -import sysUtils from 'systeminformation'; import type Logger from '@/logger.js'; export async function showMachineInfo(parentLogger: Logger) { const logger = parentLogger.createSubLogger('machine'); logger.debug(`Hostname: ${os.hostname()}`); logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`); - const mem = await sysUtils.mem(); - const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1); - const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1); - logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`); + logger.debug(`CPU: ${os.cpus().length} core MEM: ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(1)}GB (available: ${(os.freemem() / 1024 / 1024 / 1024).toFixed(1)}GB)`); } diff --git a/packages/backend/src/misc/split-id-and-objects.ts b/packages/backend/src/misc/split-id-and-objects.ts new file mode 100644 index 0000000000..d23bb93695 --- /dev/null +++ b/packages/backend/src/misc/split-id-and-objects.ts @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * idとオブジェクトを分離する + * @param input idまたはオブジェクトの配列 + * @returns idの配列とオブジェクトの配列 + */ +export function splitIdAndObjects(input: (T | string)[]): { ids: string[]; objects: T[] } { + const ids: string[] = []; + const objects : T[] = []; + + for (const item of input) { + if (typeof item === 'string') { + ids.push(item); + } else { + objects.push(item); + } + } + + return { + ids, + objects, + }; +} diff --git a/packages/backend/src/misc/unique-by-key.ts b/packages/backend/src/misc/unique-by-key.ts new file mode 100644 index 0000000000..4308e29d21 --- /dev/null +++ b/packages/backend/src/misc/unique-by-key.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * itemsの中でkey関数が返す値が重複しないようにした配列を返す + * @param items 重複を除去したい配列 + * @param key 重複判定に使うキーを返す関数 + * @returns 重複を除去した配列 + */ +export function uniqueByKey(items: Iterable, key: (item: TItem) => TKey): TItem[] { + const map = new Map(); + for (const item of items) { + const k = key(item); + if (!map.has(k)) { + map.set(k, item); + } + } + return [...map.values()]; +} diff --git a/packages/backend/src/models/AbuseReportNotificationRecipient.ts b/packages/backend/src/models/AbuseReportNotificationRecipient.ts index fbff880afc..daed81c174 100644 --- a/packages/backend/src/models/AbuseReportNotificationRecipient.ts +++ b/packages/backend/src/models/AbuseReportNotificationRecipient.ts @@ -22,7 +22,7 @@ export class MiAbuseReportNotificationRecipient { /** * 有効かどうか. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_isActive') @Column('boolean', { default: true, }) @@ -47,7 +47,7 @@ export class MiAbuseReportNotificationRecipient { /** * 通知方法. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_method') @Column('varchar', { length: 64, }) @@ -56,17 +56,18 @@ export class MiAbuseReportNotificationRecipient { /** * 通知先のユーザID. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_userId') @Column({ ...id(), nullable: true, + default: null, }) public userId: MiUser['id'] | null; /** * 通知先のユーザ. */ - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn({ name: 'userId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId1' }) @@ -75,26 +76,29 @@ export class MiAbuseReportNotificationRecipient { /** * 通知先のユーザプロフィール. */ - @ManyToOne(type => MiUserProfile, {}) + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' }) public userProfile: MiUserProfile | null; /** * 通知先のシステムWebhookId. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_systemWebhookId') @Column({ ...id(), nullable: true, + default: null, }) public systemWebhookId: string | null; /** * 通知先のシステムWebhook. */ - @ManyToOne(type => MiSystemWebhook, { + @ManyToOne(() => MiSystemWebhook, { onDelete: 'CASCADE', }) - @JoinColumn() + @JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' }) public systemWebhook: MiSystemWebhook | null; } diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index d43ebf9342..cd49fcddfe 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -18,7 +18,7 @@ export class MiAbuseUserReport { @Column(id()) public targetUserId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -28,7 +28,7 @@ export class MiAbuseUserReport { @Column(id()) public reporterId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -40,7 +40,7 @@ export class MiAbuseUserReport { }) public assigneeId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() diff --git a/packages/backend/src/models/AccessToken.ts b/packages/backend/src/models/AccessToken.ts index 6f98c14ec1..a853dcc6cb 100644 --- a/packages/backend/src/models/AccessToken.ts +++ b/packages/backend/src/models/AccessToken.ts @@ -41,7 +41,7 @@ export class MiAccessToken { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -53,7 +53,7 @@ export class MiAccessToken { }) public appId: MiApp['id'] | null; - @ManyToOne(type => MiApp, { + @ManyToOne(() => MiApp, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Ad.ts b/packages/backend/src/models/Ad.ts index 108e991c70..0d402fcbe8 100644 --- a/packages/backend/src/models/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -54,10 +54,17 @@ export class MiAd { length: 8192, nullable: false, }) public memo: string; + @Column('integer', { default: 0, nullable: false, }) public dayOfWeek: number; + + @Column('boolean', { + default: false, + }) + public isSensitive: boolean; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index d0c59fff50..f664c75262 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -79,7 +79,7 @@ export class MiAnnouncement { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/AnnouncementRead.ts b/packages/backend/src/models/AnnouncementRead.ts index 47de8dd180..2133cff140 100644 --- a/packages/backend/src/models/AnnouncementRead.ts +++ b/packages/backend/src/models/AnnouncementRead.ts @@ -18,7 +18,7 @@ export class MiAnnouncementRead { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -28,7 +28,7 @@ export class MiAnnouncementRead { @Column(id()) public announcementId: MiAnnouncement['id']; - @ManyToOne(type => MiAnnouncement, { + @ManyToOne(() => MiAnnouncement, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index 17ec0c0f79..3433cf20af 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -24,7 +24,7 @@ export class MiAntenna { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -45,7 +45,7 @@ export class MiAntenna { }) public userListId: MiUserList['id'] | null; - @ManyToOne(type => MiUserList, { + @ManyToOne(() => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() @@ -106,3 +106,6 @@ export class MiAntenna { }) public excludeNotesInSensitiveChannel: boolean; } +// Note for future developers: When you added a new column, +// You should update ExportAntennaProcessorService and ImportAntennaProcessorService +// to export and import antennas correctly. diff --git a/packages/backend/src/models/App.ts b/packages/backend/src/models/App.ts index 0185e2995c..bbb80b99ef 100644 --- a/packages/backend/src/models/App.ts +++ b/packages/backend/src/models/App.ts @@ -20,7 +20,7 @@ export class MiApp { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'SET NULL', nullable: true, }) diff --git a/packages/backend/src/models/AuthSession.ts b/packages/backend/src/models/AuthSession.ts index 03050ba955..a7273e63bf 100644 --- a/packages/backend/src/models/AuthSession.ts +++ b/packages/backend/src/models/AuthSession.ts @@ -25,7 +25,7 @@ export class MiAuthSession { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', nullable: true, }) @@ -35,7 +35,7 @@ export class MiAuthSession { @Column(id()) public appId: MiApp['id']; - @ManyToOne(type => MiApp, { + @ManyToOne(() => MiApp, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Blocking.ts b/packages/backend/src/models/Blocking.ts index 34a6efe5a6..49b584f509 100644 --- a/packages/backend/src/models/Blocking.ts +++ b/packages/backend/src/models/Blocking.ts @@ -20,7 +20,7 @@ export class MiBlocking { }) public blockeeId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -33,7 +33,7 @@ export class MiBlocking { }) public blockerId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/BubbleGameRecord.ts b/packages/backend/src/models/BubbleGameRecord.ts index 686e39c118..5dd7009fc6 100644 --- a/packages/backend/src/models/BubbleGameRecord.ts +++ b/packages/backend/src/models/BubbleGameRecord.ts @@ -18,7 +18,7 @@ export class MiBubbleGameRecord { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Channel.ts b/packages/backend/src/models/Channel.ts index f5e9b17e3e..5a5b914eb1 100644 --- a/packages/backend/src/models/Channel.ts +++ b/packages/backend/src/models/Channel.ts @@ -27,7 +27,7 @@ export class MiChannel { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() @@ -52,7 +52,7 @@ export class MiChannel { }) public bannerId: MiDriveFile['id'] | null; - @ManyToOne(type => MiDriveFile, { + @ManyToOne(() => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() diff --git a/packages/backend/src/models/ChannelFavorite.ts b/packages/backend/src/models/ChannelFavorite.ts index 167f41cf16..4f49468598 100644 --- a/packages/backend/src/models/ChannelFavorite.ts +++ b/packages/backend/src/models/ChannelFavorite.ts @@ -20,7 +20,7 @@ export class MiChannelFavorite { }) public channelId: MiChannel['id']; - @ManyToOne(type => MiChannel, { + @ManyToOne(() => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() @@ -32,7 +32,7 @@ export class MiChannelFavorite { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ChannelFollowing.ts b/packages/backend/src/models/ChannelFollowing.ts index c7afdd05b0..7597e704a8 100644 --- a/packages/backend/src/models/ChannelFollowing.ts +++ b/packages/backend/src/models/ChannelFollowing.ts @@ -21,7 +21,7 @@ export class MiChannelFollowing { }) public followeeId: MiChannel['id']; - @ManyToOne(type => MiChannel, { + @ManyToOne(() => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() @@ -34,7 +34,7 @@ export class MiChannelFollowing { }) public followerId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ChannelMuting.ts b/packages/backend/src/models/ChannelMuting.ts new file mode 100644 index 0000000000..b7054c9c5f --- /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(() => MiUser, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: MiUser | null; + + @Index() + @Column({ + ...id(), + }) + public channelId: MiChannel['id']; + + @ManyToOne(() => 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/ChatApproval.ts b/packages/backend/src/models/ChatApproval.ts index 55c9f07e9a..bd2509b67f 100644 --- a/packages/backend/src/models/ChatApproval.ts +++ b/packages/backend/src/models/ChatApproval.ts @@ -19,7 +19,7 @@ export class MiChatApproval { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -31,7 +31,7 @@ export class MiChatApproval { }) public otherId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ChatMessage.ts b/packages/backend/src/models/ChatMessage.ts index 3d2b64268e..530ef9b842 100644 --- a/packages/backend/src/models/ChatMessage.ts +++ b/packages/backend/src/models/ChatMessage.ts @@ -20,7 +20,7 @@ export class MiChatMessage { }) public fromUserId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -32,7 +32,7 @@ export class MiChatMessage { }) public toUserId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -44,7 +44,7 @@ export class MiChatMessage { }) public toRoomId: MiChatRoom['id'] | null; - @ManyToOne(type => MiChatRoom, { + @ManyToOne(() => MiChatRoom, { onDelete: 'CASCADE', }) @JoinColumn() @@ -72,7 +72,7 @@ export class MiChatMessage { }) public fileId: MiDriveFile['id'] | null; - @ManyToOne(type => MiDriveFile, { + @ManyToOne(() => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() diff --git a/packages/backend/src/models/ChatRoom.ts b/packages/backend/src/models/ChatRoom.ts index ad2a910b78..c148b16af8 100644 --- a/packages/backend/src/models/ChatRoom.ts +++ b/packages/backend/src/models/ChatRoom.ts @@ -23,7 +23,7 @@ export class MiChatRoom { }) public ownerId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ChatRoomInvitation.ts b/packages/backend/src/models/ChatRoomInvitation.ts index 36ce12bc92..5827d0401d 100644 --- a/packages/backend/src/models/ChatRoomInvitation.ts +++ b/packages/backend/src/models/ChatRoomInvitation.ts @@ -20,7 +20,7 @@ export class MiChatRoomInvitation { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -32,7 +32,7 @@ export class MiChatRoomInvitation { }) public roomId: MiChatRoom['id']; - @ManyToOne(type => MiChatRoom, { + @ManyToOne(() => MiChatRoom, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ChatRoomMembership.ts b/packages/backend/src/models/ChatRoomMembership.ts index 3cb5524859..d59b4426df 100644 --- a/packages/backend/src/models/ChatRoomMembership.ts +++ b/packages/backend/src/models/ChatRoomMembership.ts @@ -20,7 +20,7 @@ export class MiChatRoomMembership { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -32,7 +32,7 @@ export class MiChatRoomMembership { }) public roomId: MiChatRoom['id']; - @ManyToOne(type => MiChatRoom, { + @ManyToOne(() => MiChatRoom, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Clip.ts b/packages/backend/src/models/Clip.ts index 6295a329fb..ddd0298f44 100644 --- a/packages/backend/src/models/Clip.ts +++ b/packages/backend/src/models/Clip.ts @@ -25,7 +25,7 @@ export class MiClip { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ClipFavorite.ts b/packages/backend/src/models/ClipFavorite.ts index 40bdb9f4aa..2d46fd0f0e 100644 --- a/packages/backend/src/models/ClipFavorite.ts +++ b/packages/backend/src/models/ClipFavorite.ts @@ -18,7 +18,7 @@ export class MiClipFavorite { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiClipFavorite { @Column(id()) public clipId: MiClip['id']; - @ManyToOne(type => MiClip, { + @ManyToOne(() => MiClip, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/ClipNote.ts b/packages/backend/src/models/ClipNote.ts index 6e1d2bec4c..23df66c4e0 100644 --- a/packages/backend/src/models/ClipNote.ts +++ b/packages/backend/src/models/ClipNote.ts @@ -21,7 +21,7 @@ export class MiClipNote { }) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() @@ -34,7 +34,7 @@ export class MiClipNote { }) public clipId: MiClip['id']; - @ManyToOne(type => MiClip, { + @ManyToOne(() => MiClip, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts index 7b03e3e494..79189b10eb 100644 --- a/packages/backend/src/models/DriveFile.ts +++ b/packages/backend/src/models/DriveFile.ts @@ -22,7 +22,7 @@ export class MiDriveFile { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() @@ -142,7 +142,7 @@ export class MiDriveFile { }) public folderId: MiDriveFolder['id'] | null; - @ManyToOne(type => MiDriveFolder, { + @ManyToOne(() => MiDriveFolder, { onDelete: 'SET NULL', }) @JoinColumn() diff --git a/packages/backend/src/models/DriveFolder.ts b/packages/backend/src/models/DriveFolder.ts index 07046d6e11..7e34c07f46 100644 --- a/packages/backend/src/models/DriveFolder.ts +++ b/packages/backend/src/models/DriveFolder.ts @@ -26,7 +26,7 @@ export class MiDriveFolder { }) public userId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -40,7 +40,7 @@ export class MiDriveFolder { }) public parentId: MiDriveFolder['id'] | null; - @ManyToOne(type => MiDriveFolder, { + @ManyToOne(() => MiDriveFolder, { onDelete: 'SET NULL', }) @JoinColumn() diff --git a/packages/backend/src/models/Emoji.ts b/packages/backend/src/models/Emoji.ts index d62b6e9f6f..8dff8fd153 100644 --- a/packages/backend/src/models/Emoji.ts +++ b/packages/backend/src/models/Emoji.ts @@ -8,6 +8,7 @@ import { id } from './util/id.js'; @Entity('emoji') @Index(['name', 'host'], { unique: true }) +@Index('IDX_EMOJI_ROLE_IDS', { synchronize: false }) // GIN for roleIdsThatCanBeUsedThisEmojiAsReaction in production export class MiEmoji { @PrimaryColumn(id()) public id: string; @@ -32,6 +33,7 @@ export class MiEmoji { @Column('varchar', { length: 128, nullable: true, }) + @Index('IDX_EMOJI_CATEGORY') public category: string | null; @Column('varchar', { diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts index 5db7dca992..ed677a9de3 100644 --- a/packages/backend/src/models/Flash.ts +++ b/packages/backend/src/models/Flash.ts @@ -38,7 +38,7 @@ export class MiFlash { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/FlashLike.ts b/packages/backend/src/models/FlashLike.ts index a9fb48123e..0d99c2a9ae 100644 --- a/packages/backend/src/models/FlashLike.ts +++ b/packages/backend/src/models/FlashLike.ts @@ -18,7 +18,7 @@ export class MiFlashLike { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiFlashLike { @Column(id()) public flashId: MiFlash['id']; - @ManyToOne(type => MiFlash, { + @ManyToOne(() => MiFlash, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/FollowRequest.ts b/packages/backend/src/models/FollowRequest.ts index 3ff5e7a478..468829b7e8 100644 --- a/packages/backend/src/models/FollowRequest.ts +++ b/packages/backend/src/models/FollowRequest.ts @@ -20,7 +20,7 @@ export class MiFollowRequest { }) public followeeId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -33,7 +33,7 @@ export class MiFollowRequest { }) public followerId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts index 62cbc29f26..fe62166287 100644 --- a/packages/backend/src/models/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -21,7 +21,7 @@ export class MiFollowing { }) public followeeId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -34,7 +34,7 @@ export class MiFollowing { }) public followerId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/GalleryLike.ts b/packages/backend/src/models/GalleryLike.ts index ed0963122d..787b38e46d 100644 --- a/packages/backend/src/models/GalleryLike.ts +++ b/packages/backend/src/models/GalleryLike.ts @@ -18,7 +18,7 @@ export class MiGalleryLike { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiGalleryLike { @Column(id()) public postId: MiGalleryPost['id']; - @ManyToOne(type => MiGalleryPost, { + @ManyToOne(() => MiGalleryPost, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/GalleryPost.ts b/packages/backend/src/models/GalleryPost.ts index 04d8823e37..f66956628b 100644 --- a/packages/backend/src/models/GalleryPost.ts +++ b/packages/backend/src/models/GalleryPost.ts @@ -36,7 +36,7 @@ export class MiGalleryPost { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 1fbf5371bc..620853450c 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -21,7 +21,7 @@ export class MiMeta { }) public rootUserId: MiUser['id'] | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'SET NULL', nullable: true, }) @@ -59,7 +59,7 @@ export class MiMeta { public maintainerEmail: string | null; @Column('boolean', { - default: false, + default: true, }) public disableRegistration: boolean; @@ -570,7 +570,7 @@ export class MiMeta { public bannedEmailDomains: string[]; @Column('varchar', { - length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', + length: 1024, array: true, default: ['admin', 'administrator', 'root', 'system', 'maintainer', 'host', 'mod', 'moderator', 'owner', 'superuser', 'staff', 'auth', 'i', 'me', 'everyone', 'all', 'mention', 'mentions', 'example', 'user', 'users', 'account', 'accounts', 'official', 'help', 'helps', 'support', 'supports', 'info', 'information', 'informations', 'announce', 'announces', 'announcement', 'announcements', 'notice', 'notification', 'notifications', 'dev', 'developer', 'developers', 'tech', 'misskey'], }) public preservedUsernames: string[]; @@ -619,6 +619,11 @@ export class MiMeta { }) public urlPreviewEnabled: boolean; + @Column('boolean', { + default: true, + }) + public urlPreviewAllowRedirect: boolean; + @Column('integer', { default: 10000, }) @@ -630,7 +635,7 @@ export class MiMeta { public urlPreviewMaximumContentLength: number; @Column('boolean', { - default: true, + default: false, }) public urlPreviewRequireContentLength: boolean; @@ -643,12 +648,13 @@ export class MiMeta { @Column('varchar', { length: 1024, nullable: true, + default: null, }) public urlPreviewUserAgent: string | null; @Column('varchar', { length: 128, - default: 'all', + default: 'none', }) public federation: 'all' | 'specified' | 'none'; @@ -659,9 +665,74 @@ export class MiMeta { }) public federationHosts: string[]; + @Column('varchar', { + length: 128, + default: 'local', + }) + public ugcVisibilityForVisitor: 'all' | 'local' | 'none'; + @Column('varchar', { length: 64, nullable: true, }) public googleAnalyticsMeasurementId: string | null; + + @Column('jsonb', { + default: [], + }) + public deliverSuspendedSoftware: SoftwareSuspension[]; + + @Column('boolean', { + default: false, + }) + public singleUserMode: boolean; + + @Column('boolean', { + default: true, + }) + public proxyRemoteFiles: boolean; + + @Column('boolean', { + default: true, + }) + public signToActivityPubGet: boolean; + + @Column('boolean', { + default: true, + }) + public allowExternalApRedirect: boolean; + + @Column('boolean', { + default: false, + }) + public enableRemoteNotesCleaning: boolean; + + @Column('integer', { + default: 60, // minutes + }) + public remoteNotesCleaningMaxProcessingDurationInMinutes: number; + + @Column('integer', { + default: 90, // days + }) + public remoteNotesCleaningExpiryDaysForEachNotes: number; + + @Column('boolean', { + default: false, + }) + public showRoleBadgesOfRemoteUsers: boolean; + + @Column('jsonb', { + default: { }, + }) + public clientOptions: { + entrancePageStyle: 'classic' | 'simple'; + showTimelineForVisitor: boolean; + showActivitiesForVisitor: boolean; + }; } + +export type SoftwareSuspension = { + software: string, + versionRange: string, +}; diff --git a/packages/backend/src/models/ModerationLog.ts b/packages/backend/src/models/ModerationLog.ts index edde315fdf..c22114a36d 100644 --- a/packages/backend/src/models/ModerationLog.ts +++ b/packages/backend/src/models/ModerationLog.ts @@ -16,7 +16,7 @@ export class MiModerationLog { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Muting.ts b/packages/backend/src/models/Muting.ts index e1240b9c4e..9406b97a62 100644 --- a/packages/backend/src/models/Muting.ts +++ b/packages/backend/src/models/Muting.ts @@ -26,7 +26,7 @@ export class MiMuting { }) public muteeId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -39,7 +39,7 @@ export class MiMuting { }) public muterId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 9a95c6faab..089fe8f188 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -4,12 +4,24 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { noteVisibilities } from '@/types.js'; +import { noteVisibilities, noteReactionAcceptances } from '@/types.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; import { MiChannel } from './Channel.js'; import type { MiDriveFile } from './DriveFile.js'; +// Note: When you create a new index for existing column of this table, +// it might be better to index concurrently under isConcurrentIndexMigrationEnabled flag +// by editing generated migration file since this table is very large, +// and it will make a long lock to create index in most cases. +// Please note that `CREATE INDEX CONCURRENTLY` is not supported in transaction, +// so you need to set `transaction = false` in migration if isConcurrentIndexMigrationEnabled() is true. +// Please refer 1745378064470-composite-note-index.js for example. +// You should not use `@Index({ concurrent: true })` decorator because database initialization for test will fail +// because it will always run CREATE INDEX in transaction based on decorators. +// Not appending `{ concurrent: true }` to `@Index` will not cause any problem in production, + +@Index(['userId', 'id']) // Note: this index is ("userId", "id" DESC) in production, but not in test. @Entity('note') export class MiNote { @PrimaryColumn(id()) @@ -23,8 +35,8 @@ export class MiNote { }) public replyId: MiNote['id'] | null; - @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + @ManyToOne(() => MiNote, { + createForeignKeyConstraints: false, }) @JoinColumn() public reply: MiNote | null; @@ -37,8 +49,8 @@ export class MiNote { }) public renoteId: MiNote['id'] | null; - @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + @ManyToOne(() => MiNote, { + createForeignKeyConstraints: false, }) @JoinColumn() public renote: MiNote | null; @@ -65,14 +77,13 @@ export class MiNote { }) public cw: string | null; - @Index() @Column({ ...id(), comment: 'The ID of author.', }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -86,7 +97,7 @@ export class MiNote { @Column('varchar', { length: 64, nullable: true, }) - public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null; + public reactionAcceptance: typeof noteReactionAcceptances[number]; @Column('smallint', { default: 0, @@ -103,6 +114,13 @@ export class MiNote { }) public clippedCount: number; + // The number of note page blocks referencing this note. + // This column is used by Remote Note Cleaning and manually updated rather than automatically with triggers. + @Column('smallint', { + default: 0, + }) + public pageCount: number; + @Column('jsonb', { default: {}, }) @@ -190,7 +208,7 @@ export class MiNote { }) public channelId: MiChannel['id'] | null; - @ManyToOne(type => MiChannel, { + @ManyToOne(() => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() @@ -229,6 +247,13 @@ export class MiNote { comment: '[Denormalized]', }) public renoteUserHost: string | null; + + @Column({ + ...id(), + nullable: true, + comment: '[Denormalized]', + }) + public renoteChannelId: MiChannel['id'] | null; //#endregion constructor(data: Partial) { diff --git a/packages/backend/src/models/NoteDraft.ts b/packages/backend/src/models/NoteDraft.ts new file mode 100644 index 0000000000..5bfd9699fe --- /dev/null +++ b/packages/backend/src/models/NoteDraft.ts @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; +import { noteVisibilities, noteReactionAcceptances } from '@/types.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiChannel } from './Channel.js'; +import { MiNote } from './Note.js'; +import type { MiDriveFile } from './DriveFile.js'; + +@Entity('note_draft') +@Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false }) // GIN for fileIds in production +@Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false }) // GIN for visibleUserIds in production +export class MiNoteDraft { + @PrimaryColumn(id()) + public id: string; + + @Index('IDX_NOTE_DRAFT_REPLY_ID') + @Column({ + ...id(), + nullable: true, + comment: 'The ID of reply target.', + }) + public replyId: MiNote['id'] | null; + + // There is a possibility that replyId is not null but reply is null when the reply note is deleted. + @ManyToOne(() => MiNote, { + createForeignKeyConstraints: false, + }) + @JoinColumn() + public reply: MiNote | null; + + @Index('IDX_NOTE_DRAFT_RENOTE_ID') + @Column({ + ...id(), + nullable: true, + comment: 'The ID of renote target.', + }) + public renoteId: MiNote['id'] | null; + + // There is a possibility that renoteId is not null but renote is null when the renote note is deleted. + @ManyToOne(() => MiNote, { + createForeignKeyConstraints: false, + }) + @JoinColumn() + public renote: MiNote | null; + + // TODO: varcharにしたい(Note.tsと同じ) + @Column('text', { + nullable: true, + }) + public text: string | null; + + @Column('varchar', { + length: 512, nullable: true, + }) + public cw: string | null; + + @Index('IDX_NOTE_DRAFT_USER_ID') + @Column({ + ...id(), + comment: 'The ID of author.', + }) + public userId: MiUser['id']; + + @ManyToOne(() => MiUser, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: MiUser | null; + + @Column('boolean', { + default: false, + }) + public localOnly: boolean; + + @Column('varchar', { + length: 64, nullable: true, + }) + public reactionAcceptance: typeof noteReactionAcceptances[number]; + + /** + * public ... 公開 + * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す + * followers ... フォロワーのみ + * specified ... visibleUserIds で指定したユーザーのみ + */ + @Column('enum', { enum: noteVisibilities }) + public visibility: typeof noteVisibilities[number]; + + @Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false }) + @Column({ + ...id(), + array: true, default: '{}', + }) + public fileIds: MiDriveFile['id'][]; + + @Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false }) + @Column({ + ...id(), + array: true, default: '{}', + }) + public visibleUserIds: MiUser['id'][]; + + @Column('varchar', { + length: 128, nullable: true, + }) + public hashtag: string | null; + + @Index('IDX_NOTE_DRAFT_CHANNEL_ID') + @Column({ + ...id(), + nullable: true, + comment: 'The ID of source channel.', + }) + public channelId: MiChannel['id'] | null; + + // There is a possibility that channelId is not null but channel is null when the channel is deleted. + // (deleting channel is not implemented so it's not happening now but may happen in the future) + @ManyToOne(() => MiChannel, { + createForeignKeyConstraints: false, + }) + @JoinColumn() + public channel: MiChannel | null; + + //#region 以下、Pollについて追加 + + @Column('boolean', { + default: false, + }) + public hasPoll: boolean; + + @Column('varchar', { + length: 256, array: true, default: '{}', + }) + public pollChoices: string[]; + + @Column('boolean') + public pollMultiple: boolean; + + @Column('timestamp with time zone', { + nullable: true, + }) + public pollExpiresAt: Date | null; + + @Column('bigint', { + nullable: true, + }) + public pollExpiredAfter: number | null; + + //#endregion + + // 予約日時 + // これがあるだけでは実際に予約されているかどうかはわからない + @Column('timestamp with time zone', { + nullable: true, + }) + public scheduledAt: Date | null; + + // scheduledAtに基づいて実際にスケジュールされているか + @Column('boolean', { + default: false, + }) + public isActuallyScheduled: boolean; +} diff --git a/packages/backend/src/models/NoteFavorite.ts b/packages/backend/src/models/NoteFavorite.ts index cf76c767b0..0e498eb70d 100644 --- a/packages/backend/src/models/NoteFavorite.ts +++ b/packages/backend/src/models/NoteFavorite.ts @@ -18,7 +18,7 @@ export class MiNoteFavorite { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiNoteFavorite { @Column(id()) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts index 42dfcaa9ad..98263081ab 100644 --- a/packages/backend/src/models/NoteReaction.ts +++ b/packages/backend/src/models/NoteReaction.ts @@ -18,7 +18,7 @@ export class MiNoteReaction { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -28,7 +28,7 @@ export class MiNoteReaction { @Column(id()) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/NoteThreadMuting.ts b/packages/backend/src/models/NoteThreadMuting.ts index e7bd39f348..32bb829c0b 100644 --- a/packages/backend/src/models/NoteThreadMuting.ts +++ b/packages/backend/src/models/NoteThreadMuting.ts @@ -19,7 +19,7 @@ export class MiNoteThreadMuting { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 5764a307b0..7fa17e20fa 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -9,7 +9,9 @@ import { MiNote } from './Note.js'; import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; import { MiDriveFile } from './DriveFile.js'; +import { MiNoteDraft } from './NoteDraft.js'; +// misskey-js の notificationTypes と同期すべし export type MiNotification = { type: 'note'; id: string; @@ -59,6 +61,16 @@ export type MiNotification = { createdAt: string; notifierId: MiUser['id']; noteId: MiNote['id']; +} | { + type: 'scheduledNotePosted'; + id: string; + createdAt: string; + noteId: MiNote['id']; +} | { + type: 'scheduledNotePostFailed'; + id: string; + createdAt: string; + noteDraftId: MiNoteDraft['id']; } | { type: 'receiveFollowRequest'; id: string; diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 0b59e7a92c..8811200801 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -47,7 +47,7 @@ export class MiPage { @Column('varchar', { length: 32, }) - public font: string; + public font: 'serif' | 'sans-serif'; @Index() @Column({ @@ -56,7 +56,7 @@ export class MiPage { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -68,8 +68,8 @@ export class MiPage { }) public eyeCatchingImageId: MiDriveFile['id'] | null; - @ManyToOne(type => MiDriveFile, { - onDelete: 'CASCADE', + @ManyToOne(() => MiDriveFile, { + onDelete: 'SET NULL', }) @JoinColumn() public eyeCatchingImage: MiDriveFile | null; diff --git a/packages/backend/src/models/PageLike.ts b/packages/backend/src/models/PageLike.ts index 05ca22cf2c..cf3025ae1c 100644 --- a/packages/backend/src/models/PageLike.ts +++ b/packages/backend/src/models/PageLike.ts @@ -18,7 +18,7 @@ export class MiPageLike { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiPageLike { @Column(id()) public pageId: MiPage['id']; - @ManyToOne(type => MiPage, { + @ManyToOne(() => MiPage, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/PasswordResetRequest.ts b/packages/backend/src/models/PasswordResetRequest.ts index fdaf21056b..3379b540ee 100644 --- a/packages/backend/src/models/PasswordResetRequest.ts +++ b/packages/backend/src/models/PasswordResetRequest.ts @@ -24,7 +24,7 @@ export class MiPasswordResetRequest { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Poll.ts b/packages/backend/src/models/Poll.ts index ca985c8b24..d82e29fb85 100644 --- a/packages/backend/src/models/Poll.ts +++ b/packages/backend/src/models/Poll.ts @@ -15,7 +15,7 @@ export class MiPoll { @PrimaryColumn(id()) public noteId: MiNote['id']; - @OneToOne(type => MiNote, { + @OneToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/PollVote.ts b/packages/backend/src/models/PollVote.ts index b5c780293c..600ca8ea41 100644 --- a/packages/backend/src/models/PollVote.ts +++ b/packages/backend/src/models/PollVote.ts @@ -18,7 +18,7 @@ export class MiPollVote { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -28,7 +28,7 @@ export class MiPollVote { @Column(id()) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/PromoNote.ts b/packages/backend/src/models/PromoNote.ts index ae27adec9e..871f7471fc 100644 --- a/packages/backend/src/models/PromoNote.ts +++ b/packages/backend/src/models/PromoNote.ts @@ -13,7 +13,7 @@ export class MiPromoNote { @PrimaryColumn(id()) public noteId: MiNote['id']; - @OneToOne(type => MiNote, { + @OneToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/PromoRead.ts b/packages/backend/src/models/PromoRead.ts index b2a698cc7b..15a3573ef3 100644 --- a/packages/backend/src/models/PromoRead.ts +++ b/packages/backend/src/models/PromoRead.ts @@ -18,7 +18,7 @@ export class MiPromoRead { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiPromoRead { @Column(id()) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/RegistrationTicket.ts b/packages/backend/src/models/RegistrationTicket.ts index 0a4e4b9189..07216599d3 100644 --- a/packages/backend/src/models/RegistrationTicket.ts +++ b/packages/backend/src/models/RegistrationTicket.ts @@ -23,7 +23,7 @@ export class MiRegistrationTicket { }) public expiresAt: Date | null; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -36,7 +36,7 @@ export class MiRegistrationTicket { }) public createdById: MiUser['id'] | null; - @OneToOne(type => MiUser, { + @OneToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/RegistryItem.ts b/packages/backend/src/models/RegistryItem.ts index 335e8b9eab..869980bbff 100644 --- a/packages/backend/src/models/RegistryItem.ts +++ b/packages/backend/src/models/RegistryItem.ts @@ -25,7 +25,7 @@ export class MiRegistryItem { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/RenoteMuting.ts b/packages/backend/src/models/RenoteMuting.ts index 448a0b7663..b760a09c53 100644 --- a/packages/backend/src/models/RenoteMuting.ts +++ b/packages/backend/src/models/RenoteMuting.ts @@ -20,7 +20,7 @@ export class MiRenoteMuting { }) public muteeId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -33,7 +33,7 @@ export class MiRenoteMuting { }) public muterId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index eb097eafae..607d777b1b 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, @@ -42,6 +43,7 @@ import { MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, + MiNoteDraft, MiPage, MiPageLike, MiPasswordResetRequest, @@ -141,6 +143,12 @@ const $noteReactionsRepository: Provider = { inject: [DI.db], }; +const $noteDraftsRepository: Provider = { + provide: DI.noteDraftsRepository, + useFactory: (db: DataSource) => db.getRepository(MiNoteDraft).extend(miRepository as MiRepository), + inject: [DI.db], +}; + const $pollsRepository: Provider = { provide: DI.pollsRepository, useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository), @@ -423,6 +431,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), @@ -549,6 +563,7 @@ const $mahjongGamesRepository: Provider = { $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, + $noteDraftsRepository, $pollsRepository, $pollVotesRepository, $userProfilesRepository, @@ -596,6 +611,7 @@ const $mahjongGamesRepository: Provider = { $channelsRepository, $channelFollowingsRepository, $channelFavoritesRepository, + $channelMutingRepository, $registryItemsRepository, $webhooksRepository, $systemWebhooksRepository, @@ -626,6 +642,7 @@ const $mahjongGamesRepository: Provider = { $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, + $noteDraftsRepository, $pollsRepository, $pollVotesRepository, $userProfilesRepository, @@ -673,6 +690,7 @@ const $mahjongGamesRepository: Provider = { $channelsRepository, $channelFollowingsRepository, $channelFavoritesRepository, + $channelMutingRepository, $registryItemsRepository, $webhooksRepository, $systemWebhooksRepository, diff --git a/packages/backend/src/models/ReversiGame.ts b/packages/backend/src/models/ReversiGame.ts index 6b29a0ce8c..fbbf24792f 100644 --- a/packages/backend/src/models/ReversiGame.ts +++ b/packages/backend/src/models/ReversiGame.ts @@ -27,7 +27,7 @@ export class MiReversiGame { @Column(id()) public user1Id: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -36,7 +36,7 @@ export class MiReversiGame { @Column(id()) public user2Id: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/RoleAssignment.ts b/packages/backend/src/models/RoleAssignment.ts index 37755d631b..cb96377f66 100644 --- a/packages/backend/src/models/RoleAssignment.ts +++ b/packages/backend/src/models/RoleAssignment.ts @@ -21,7 +21,7 @@ export class MiRoleAssignment { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -34,7 +34,7 @@ export class MiRoleAssignment { }) public roleId: MiRole['id']; - @ManyToOne(type => MiRole, { + @ManyToOne(() => MiRole, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Signin.ts b/packages/backend/src/models/Signin.ts index f8ff9c57d7..59cbad735d 100644 --- a/packages/backend/src/models/Signin.ts +++ b/packages/backend/src/models/Signin.ts @@ -16,7 +16,7 @@ export class MiSignin { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/SwSubscription.ts b/packages/backend/src/models/SwSubscription.ts index 0c531132b3..a95aede44f 100644 --- a/packages/backend/src/models/SwSubscription.ts +++ b/packages/backend/src/models/SwSubscription.ts @@ -16,7 +16,7 @@ export class MiSwSubscription { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/SystemAccount.ts b/packages/backend/src/models/SystemAccount.ts index f32880b81d..2a48e62ed1 100644 --- a/packages/backend/src/models/SystemAccount.ts +++ b/packages/backend/src/models/SystemAccount.ts @@ -18,7 +18,7 @@ export class MiSystemAccount { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index baf4eefdf1..084dd35485 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -99,7 +99,7 @@ export class MiUser { }) public avatarId: MiDriveFile['id'] | null; - @OneToOne(type => MiDriveFile, { + @OneToOne(() => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() @@ -112,7 +112,7 @@ export class MiUser { }) public bannerId: MiDriveFile['id'] | null; - @OneToOne(type => MiDriveFile, { + @OneToOne(() => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() @@ -120,7 +120,7 @@ export class MiUser { // avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること @Column('varchar', { - length: 512, nullable: true, + length: 1024, nullable: true, }) public avatarUrl: string | null; diff --git a/packages/backend/src/models/UserKeypair.ts b/packages/backend/src/models/UserKeypair.ts index f5252d126c..894739c84c 100644 --- a/packages/backend/src/models/UserKeypair.ts +++ b/packages/backend/src/models/UserKeypair.ts @@ -12,7 +12,7 @@ export class MiUserKeypair { @PrimaryColumn(id()) public userId: MiUser['id']; - @OneToOne(type => MiUser, { + @OneToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserList.ts b/packages/backend/src/models/UserList.ts index 5fb991a87d..05fd833b6f 100644 --- a/packages/backend/src/models/UserList.ts +++ b/packages/backend/src/models/UserList.ts @@ -25,7 +25,7 @@ export class MiUserList { }) public isPublic: boolean; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserListFavorite.ts b/packages/backend/src/models/UserListFavorite.ts index 80b2d61eb7..67ab92d98c 100644 --- a/packages/backend/src/models/UserListFavorite.ts +++ b/packages/backend/src/models/UserListFavorite.ts @@ -18,7 +18,7 @@ export class MiUserListFavorite { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiUserListFavorite { @Column(id()) public userListId: MiUserList['id']; - @ManyToOne(type => MiUserList, { + @ManyToOne(() => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListMembership.ts index af659d071d..1a2b3fffc1 100644 --- a/packages/backend/src/models/UserListMembership.ts +++ b/packages/backend/src/models/UserListMembership.ts @@ -21,7 +21,7 @@ export class MiUserListMembership { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -34,7 +34,7 @@ export class MiUserListMembership { }) public userListId: MiUserList['id']; - @ManyToOne(type => MiUserList, { + @ManyToOne(() => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserMemo.ts b/packages/backend/src/models/UserMemo.ts index 29e28d290a..facc8c6b1c 100644 --- a/packages/backend/src/models/UserMemo.ts +++ b/packages/backend/src/models/UserMemo.ts @@ -20,7 +20,7 @@ export class MiUserMemo { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -33,7 +33,7 @@ export class MiUserMemo { }) public targetUserId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserNotePining.ts b/packages/backend/src/models/UserNotePining.ts index 92c5cd55d0..950da2ad22 100644 --- a/packages/backend/src/models/UserNotePining.ts +++ b/packages/backend/src/models/UserNotePining.ts @@ -18,7 +18,7 @@ export class MiUserNotePining { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -27,7 +27,7 @@ export class MiUserNotePining { @Column(id()) public noteId: MiNote['id']; - @ManyToOne(type => MiNote, { + @ManyToOne(() => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 5544555296..b05bf14ef9 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -17,7 +17,7 @@ export class MiUserProfile { @PrimaryColumn(id()) public userId: MiUser['id']; - @OneToOne(type => MiUser, { + @OneToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() @@ -29,7 +29,7 @@ export class MiUserProfile { }) public location: string | null; - @Index() + // Note: There's index named IDX_de22cd2b445eee31ae51cdbe99 for SUBSTR("birthday", 6, 5) @Column('char', { length: 10, nullable: true, comment: 'The birthday (YYYY-MM-DD) of the User.', @@ -215,7 +215,7 @@ export class MiUserProfile { }) public pinnedPageId: MiPage['id'] | null; - @OneToOne(type => MiPage, { + @OneToOne(() => MiPage, { onDelete: 'SET NULL', }) @JoinColumn() @@ -274,7 +274,7 @@ export class MiUserProfile { default: [], }) public achievements: { - name: string; + name: typeof ACHIEVEMENT_TYPES[number]; unlockedAt: number; }[]; @@ -295,3 +295,84 @@ export class MiUserProfile { } } } + +export const ACHIEVEMENT_TYPES = [ + 'notes1', + 'notes10', + 'notes100', + 'notes500', + 'notes1000', + 'notes5000', + 'notes10000', + 'notes20000', + 'notes30000', + 'notes40000', + 'notes50000', + 'notes60000', + 'notes70000', + 'notes80000', + 'notes90000', + 'notes100000', + 'login3', + 'login7', + 'login15', + 'login30', + 'login60', + 'login100', + 'login200', + 'login300', + 'login400', + 'login500', + 'login600', + 'login700', + 'login800', + 'login900', + 'login1000', + 'passedSinceAccountCreated1', + 'passedSinceAccountCreated2', + 'passedSinceAccountCreated3', + 'loggedInOnBirthday', + 'loggedInOnNewYearsDay', + 'noteClipped1', + 'noteFavorited1', + 'myNoteFavorited1', + 'profileFilled', + 'markedAsCat', + 'following1', + 'following10', + 'following50', + 'following100', + 'following300', + 'followers1', + 'followers10', + 'followers50', + 'followers100', + 'followers300', + 'followers500', + 'followers1000', + 'collectAchievements30', + 'viewAchievements3min', + 'iLoveMisskey', + 'foundTreasure', + 'client30min', + 'client60min', + 'noteDeletedWithin1min', + 'postedAtLateNight', + 'postedAt0min0sec', + 'selfQuote', + 'htl20npm', + 'viewInstanceChart', + 'outputHelloWorldOnScratchpad', + 'open3windows', + 'driveFolderCircularReference', + 'reactWithoutRead', + 'clickedClickHere', + 'justPlainLucky', + 'setNameToSyuilo', + 'cookieClicked', + 'brainDiver', + 'smashTestNotificationButton', + 'tutorialCompleted', + 'bubbleGameExplodingHead', + 'bubbleGameDoubleExplodingHead', +] as const; diff --git a/packages/backend/src/models/UserPublickey.ts b/packages/backend/src/models/UserPublickey.ts index 6bcd785304..8c23d368e9 100644 --- a/packages/backend/src/models/UserPublickey.ts +++ b/packages/backend/src/models/UserPublickey.ts @@ -12,7 +12,7 @@ export class MiUserPublickey { @PrimaryColumn(id()) public userId: MiUser['id']; - @OneToOne(type => MiUser, { + @OneToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/UserSecurityKey.ts b/packages/backend/src/models/UserSecurityKey.ts index 0babbe1abe..577ec359e4 100644 --- a/packages/backend/src/models/UserSecurityKey.ts +++ b/packages/backend/src/models/UserSecurityKey.ts @@ -18,7 +18,7 @@ export class MiUserSecurityKey { @Column(id()) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts index b4cab4edc8..5f833115cc 100644 --- a/packages/backend/src/models/Webhook.ts +++ b/packages/backend/src/models/Webhook.ts @@ -22,7 +22,7 @@ export class MiWebhook { }) public userId: MiUser['id']; - @ManyToOne(type => MiUser, { + @ManyToOne(() => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 75d623be0d..d789de418a 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -5,18 +5,9 @@ import { FindOneOptions, - InsertQueryBuilder, ObjectLiteral, - QueryRunner, Repository, - SelectQueryBuilder, } from 'typeorm'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; -import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js'; -import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js'; -import { - RawSqlResultsToEntityTransformer, -} from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js'; import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js'; import { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { MiAccessToken } from '@/models/AccessToken.js'; @@ -32,6 +23,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'; @@ -56,6 +48,7 @@ import { MiMeta } from '@/models/Meta.js'; import { MiModerationLog } from '@/models/ModerationLog.js'; import { MiMuting } from '@/models/Muting.js'; import { MiNote } from '@/models/Note.js'; +import { MiNoteDraft } from '@/models/NoteDraft.js'; import { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { MiNoteReaction } from '@/models/NoteReaction.js'; import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js'; @@ -95,66 +88,12 @@ import { MiWebhook } from '@/models/Webhook.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; export interface MiRepository { - createTableColumnNames(this: Repository & MiRepository): string[]; - insertOne(this: Repository & MiRepository, entity: QueryDeepPartialEntity, findOptions?: Pick, 'relations'>): Promise; - - insertOneImpl(this: Repository & MiRepository, entity: QueryDeepPartialEntity, findOptions?: Pick, 'relations'>, queryRunner?: QueryRunner): Promise; - - selectAliasColumnNames(this: Repository & MiRepository, queryBuilder: InsertQueryBuilder, builder: SelectQueryBuilder): void; } export const miRepository = { - createTableColumnNames() { - return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName); - }, async insertOne(entity, findOptions?) { - const opt = this.manager.connection.options as PostgresConnectionOptions; - if (opt.replication) { - const queryRunner = this.manager.connection.createQueryRunner('master'); - try { - return this.insertOneImpl(entity, findOptions, queryRunner); - } finally { - await queryRunner.release(); - } - } else { - return this.insertOneImpl(entity, findOptions); - } - }, - async insertOneImpl(entity, findOptions?, queryRunner?) { - // ---- insert + returningの結果を共通テーブル式(CTE)に保持するクエリを生成 ---- - - const queryBuilder = this.createQueryBuilder().insert().values(entity); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const mainAlias = queryBuilder.expressionMap.mainAlias!; - const name = mainAlias.name; - mainAlias.name = 't'; - const columnNames = this.createTableColumnNames(); - queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2)); - - // ---- 共通テーブル式(CTE)から結果を取得 ---- - const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - builder.expressionMap.mainAlias!.tablePath = 'cte'; - this.selectAliasColumnNames(queryBuilder, builder); - if (findOptions) { - builder.setFindOptions(findOptions); - } - const raw = await builder.execute(); - mainAlias.name = name; - const relationId = await new RelationIdLoader(builder.connection, this.queryRunner, builder.expressionMap.relationIdAttributes).load(raw); - const relationCount = await new RelationCountLoader(builder.connection, this.queryRunner, builder.expressionMap.relationCountAttributes).load(raw); - const result = new RawSqlResultsToEntityTransformer(builder.expressionMap, builder.connection.driver, relationId, relationCount, this.queryRunner).transform(raw, mainAlias); - return result[0]; - }, - selectAliasColumnNames(queryBuilder, builder) { - let selectOrAddSelect = (selection: string, selectionAliasName?: string) => { - selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName); - return builder.select(selection, selectionAliasName); - }; - for (const columnName of this.createTableColumnNames()) { - selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`); - } + return await this.insert(entity).then(x => this.findOneOrFail({ where: x.identifiers[0], ...findOptions })); }, } satisfies MiRepository; @@ -172,6 +111,7 @@ export { MiBlocking, MiChannelFollowing, MiChannelFavorite, + MiChannelMuting, MiClip, MiClipNote, MiClipFavorite, @@ -189,6 +129,7 @@ export { MiMuting, MiRenoteMuting, MiNote, + MiNoteDraft, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, @@ -251,6 +192,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; @@ -268,6 +210,7 @@ export type ModerationLogsRepository = Repository & MiRepositor export type MutingsRepository = Repository & MiRepository; export type RenoteMutingsRepository = Repository & MiRepository; export type NotesRepository = Repository & MiRepository; +export type NoteDraftsRepository = Repository & MiRepository; export type NoteFavoritesRepository = Repository & MiRepository; export type NoteReactionsRepository = Repository & MiRepository; export type NoteThreadMutingsRepository = Repository & MiRepository; diff --git a/packages/backend/src/models/json-schema/achievement.ts b/packages/backend/src/models/json-schema/achievement.ts new file mode 100644 index 0000000000..39a621a570 --- /dev/null +++ b/packages/backend/src/models/json-schema/achievement.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; + +export const packedAchievementNameSchema = { + type: 'string', + enum: ACHIEVEMENT_TYPES, + optional: false, +} as const; + +export const packedAchievementSchema = { + type: 'object', + properties: { + name: { + ref: 'AchievementName', + }, + unlockedAt: { + type: 'number', + optional: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/ad.ts b/packages/backend/src/models/json-schema/ad.ts index b01b39a38b..d88ac23894 100644 --- a/packages/backend/src/models/json-schema/ad.ts +++ b/packages/backend/src/models/json-schema/ad.ts @@ -60,5 +60,10 @@ export const packedAdSchema = { optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts index d233f7858d..e7613290d1 100644 --- a/packages/backend/src/models/json-schema/channel.ts +++ b/packages/backend/src/models/json-schema/channel.ts @@ -40,6 +40,11 @@ export const packedChannelSchema = { format: 'url', nullable: true, optional: false, }, + bannerId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, pinnedNoteIds: { type: 'array', nullable: false, optional: false, @@ -80,6 +85,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/models/json-schema/chat-room.ts b/packages/backend/src/models/json-schema/chat-room.ts index e97556e378..e628a9baa3 100644 --- a/packages/backend/src/models/json-schema/chat-room.ts +++ b/packages/backend/src/models/json-schema/chat-room.ts @@ -36,5 +36,9 @@ export const packedChatRoomSchema = { type: 'boolean', optional: true, nullable: false, }, + invitationExists: { + type: 'boolean', + optional: true, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 912a0399d8..85f84952f1 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -48,7 +48,7 @@ export const packedFederationInstanceSchema = { suspensionState: { type: 'string', nullable: false, optional: false, - enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'], + enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding', 'softwareSuspended'], }, isBlocked: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/flash.ts b/packages/backend/src/models/json-schema/flash.ts index 42b2172409..d50200e6e9 100644 --- a/packages/backend/src/models/json-schema/flash.ts +++ b/packages/backend/src/models/json-schema/flash.ts @@ -51,7 +51,7 @@ export const packedFlashSchema = { }, likedCount: { type: 'number', - optional: false, nullable: true, + optional: false, nullable: false, }, isLiked: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 2cd7620af0..0c3ec141bc 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -71,6 +71,9 @@ export const packedMetaLiteSchema = { type: 'string', optional: false, nullable: true, }, + clientOptions: { + ref: 'MetaClientOptions', + }, disableRegistration: { type: 'boolean', optional: false, nullable: false, @@ -191,6 +194,10 @@ export const packedMetaLiteSchema = { type: 'integer', optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: true, nullable: false, + }, }, }, }, @@ -389,3 +396,23 @@ export const packedMetaDetailedSchema = { }, ], } as const; + +export const packedMetaClientOptionsSchema = { + type: 'object', + optional: false, nullable: false, + properties: { + entrancePageStyle: { + type: 'string', + enum: ['classic', 'simple'], + optional: false, nullable: false, + }, + showTimelineForVisitor: { + type: 'boolean', + optional: false, nullable: false, + }, + showActivitiesForVisitor: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/note-draft.ts b/packages/backend/src/models/json-schema/note-draft.ts new file mode 100644 index 0000000000..8144ac7b3b --- /dev/null +++ b/packages/backend/src/models/json-schema/note-draft.ts @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const packedNoteDraftSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + cw: { + type: 'string', + optional: false, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + replyId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + renoteId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + reply: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + renote: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + visibility: { + type: 'string', + optional: false, nullable: false, + enum: ['public', 'home', 'followers', 'specified'], + }, + visibleUserIds: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + fileIds: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + hashtag: { + type: 'string', + optional: false, nullable: true, + }, + poll: { + type: 'object', + optional: false, nullable: true, + properties: { + expiresAt: { + type: 'string', + optional: true, nullable: true, + format: 'date-time', + }, + expiredAfter: { + type: 'number', + optional: true, nullable: true, + }, + multiple: { + type: 'boolean', + optional: false, nullable: false, + }, + choices: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + }, + channelId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + channel: { + type: 'object', + optional: true, nullable: true, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + color: { + type: 'string', + optional: false, nullable: false, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + allowRenoteToExternal: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + localOnly: { + type: 'boolean', + optional: false, nullable: false, + }, + reactionAcceptance: { + type: 'string', + optional: false, nullable: true, + enum: ['likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote', null], + }, + scheduledAt: { + type: 'number', + optional: false, nullable: true, + }, + isActuallyScheduled: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts index 95658ace1f..04c9f34232 100644 --- a/packages/backend/src/models/json-schema/note-reaction.ts +++ b/packages/backend/src/models/json-schema/note-reaction.ts @@ -10,7 +10,6 @@ export const packedNoteReactionSchema = { type: 'string', optional: false, nullable: false, format: 'id', - example: 'xxxxxxxxxx', }, createdAt: { type: 'string', @@ -28,3 +27,33 @@ export const packedNoteReactionSchema = { }, }, } as const; + +export const packedNoteReactionWithNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 432c096e48..f3901691a4 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -256,6 +256,10 @@ export const packedNoteSchema = { type: 'number', optional: true, nullable: false, }, + hasPoll: { + type: 'boolean', + optional: true, nullable: false, + }, myReaction: { type: 'string', diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index 7f23d2d6a1..30e9c9327a 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; import { notificationTypes, userExportableEntities } from '@/types.js'; const baseSchema = { @@ -208,6 +207,36 @@ export const packedNotificationSchema = { optional: false, nullable: false, }, }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['scheduledNotePosted'], + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['scheduledNotePostFailed'], + }, + noteDraft: { + type: 'object', + ref: 'NoteDraft', + optional: false, nullable: false, + }, + }, }, { type: 'object', properties: { @@ -312,9 +341,7 @@ export const packedNotificationSchema = { enum: ['achievementEarned'], }, achievement: { - type: 'string', - optional: false, nullable: false, - enum: ACHIEVEMENT_TYPES, + ref: 'AchievementName', }, }, }, { diff --git a/packages/backend/src/models/json-schema/page.ts b/packages/backend/src/models/json-schema/page.ts index 748d6f1245..8f6d5c675d 100644 --- a/packages/backend/src/models/json-schema/page.ts +++ b/packages/backend/src/models/json-schema/page.ts @@ -174,6 +174,7 @@ export const packedPageSchema = { font: { type: 'string', optional: false, nullable: false, + enum: ['serif', 'sans-serif'], }, script: { type: 'string', diff --git a/packages/backend/src/models/json-schema/queue.ts b/packages/backend/src/models/json-schema/queue.ts index 2ecf5c831f..dad0cf57f6 100644 --- a/packages/backend/src/models/json-schema/queue.ts +++ b/packages/backend/src/models/json-schema/queue.ts @@ -28,3 +28,110 @@ export const packedQueueCountSchema = { }, }, } as const; + +// Bull.Metrics +export const packedQueueMetricsSchema = { + type: 'object', + properties: { + meta: { + type: 'object', + optional: false, nullable: false, + properties: { + count: { + type: 'number', + optional: false, nullable: false, + }, + prevTS: { + type: 'number', + optional: false, nullable: false, + }, + prevCount: { + type: 'number', + optional: false, nullable: false, + }, + }, + }, + data: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'number', + optional: false, nullable: false, + }, + }, + count: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; + +export const packedQueueJobSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + data: { + type: 'object', + optional: false, nullable: false, + }, + opts: { + type: 'object', + optional: false, nullable: false, + }, + timestamp: { + type: 'number', + optional: false, nullable: false, + }, + processedOn: { + type: 'number', + optional: true, nullable: false, + }, + processedBy: { + type: 'string', + optional: true, nullable: false, + }, + finishedOn: { + type: 'number', + optional: true, nullable: false, + }, + progress: { + type: 'object', + optional: false, nullable: false, + }, + attempts: { + type: 'number', + optional: false, nullable: false, + }, + delay: { + type: 'number', + optional: false, nullable: false, + }, + failedReason: { + type: 'string', + optional: false, nullable: false, + }, + stacktrace: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + returnValue: { + type: 'object', + optional: false, nullable: false, + }, + isFailed: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts index cb37200384..378ae41cb5 100644 --- a/packages/backend/src/models/json-schema/reversi-game.ts +++ b/packages/backend/src/models/json-schema/reversi-game.ts @@ -81,6 +81,7 @@ export const packedReversiGameLiteSchema = { bw: { type: 'string', optional: false, nullable: false, + enum: ['random', '1', '2'], }, noIrregularRules: { type: 'boolean', @@ -199,6 +200,7 @@ export const packedReversiGameDetailedSchema = { bw: { type: 'string', optional: false, nullable: false, + enum: ['random', '1', '2'], }, noIrregularRules: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 1cfcb830e0..b9000152d4 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -212,6 +212,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canSearchUsers: { + type: 'boolean', + optional: false, nullable: false, + }, canUseTranslator: { type: 'boolean', optional: false, nullable: false, @@ -224,6 +228,18 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + maxFileSizeMb: { + type: 'integer', + optional: false, nullable: false, + }, + uploadableFileTypes: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, alwaysMarkNsfw: { type: 'boolean', optional: false, nullable: false, @@ -297,6 +313,18 @@ export const packedRolePoliciesSchema = { optional: false, nullable: false, enum: ['available', 'readonly', 'unavailable'], }, + noteDraftLimit: { + type: 'integer', + optional: false, nullable: false, + }, + scheduledNoteLimit: { + type: 'integer', + optional: false, nullable: false, + }, + watermarkAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/user-webhook.ts b/packages/backend/src/models/json-schema/user-webhook.ts new file mode 100644 index 0000000000..8ea0991716 --- /dev/null +++ b/packages/backend/src/models/json-schema/user-webhook.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { webhookEventTypes } from '@/models/Webhook.js'; + +export const packedUserWebhookSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + userId: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + on: { + type: 'array', + items: { + type: 'string', + optional: false, nullable: false, + enum: webhookEventTypes, + }, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + secret: { + type: 'string', + optional: false, nullable: false, + }, + active: { + type: 'boolean', + optional: false, nullable: false, + }, + latestSentAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: true, + }, + latestStatus: { + type: 'integer', + optional: false, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index e475296702..f71ec1d023 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -65,7 +65,7 @@ export const packedUserLiteSchema = { avatarUrl: { type: 'string', format: 'url', - nullable: true, optional: false, + nullable: false, optional: false, }, avatarBlurhash: { type: 'string', @@ -465,11 +465,11 @@ export const packedMeDetailedOnlySchema = { }, isModerator: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, isAdmin: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, injectFeaturedNote: { type: 'boolean', @@ -591,7 +591,7 @@ export const packedMeDetailedOnlySchema = { }, mutedInstances: { type: 'array', - nullable: true, optional: false, + nullable: false, optional: false, items: { type: 'string', nullable: false, optional: false, @@ -609,6 +609,8 @@ export const packedMeDetailedOnlySchema = { quote: { optional: true, ...notificationRecieveConfig }, reaction: { optional: true, ...notificationRecieveConfig }, pollEnded: { optional: true, ...notificationRecieveConfig }, + scheduledNotePosted: { optional: true, ...notificationRecieveConfig }, + scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig }, receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, @@ -616,6 +618,9 @@ export const packedMeDetailedOnlySchema = { achievementEarned: { optional: true, ...notificationRecieveConfig }, app: { optional: true, ...notificationRecieveConfig }, test: { optional: true, ...notificationRecieveConfig }, + login: { optional: true, ...notificationRecieveConfig }, + createToken: { optional: true, ...notificationRecieveConfig }, + exportCompleted: { optional: true, ...notificationRecieveConfig }, }, }, emailNotificationTypes: { @@ -630,18 +635,7 @@ export const packedMeDetailedOnlySchema = { type: 'array', nullable: false, optional: false, items: { - type: 'object', - nullable: false, optional: false, - properties: { - name: { - type: 'string', - nullable: false, optional: false, - }, - unlockedAt: { - type: 'number', - nullable: false, optional: false, - }, - }, + ref: 'Achievement', }, }, loggedInDays: { diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 1ddd9c507f..c5b6b2c94b 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -6,7 +6,6 @@ // https://github.com/typeorm/typeorm/issues/2400 import pg from 'pg'; import { DataSource, Logger, type QueryRunner } from 'typeorm'; -import * as highlight from 'cli-highlight'; import { entities as charts } from '@/core/chart/entities.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -25,6 +24,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'; @@ -45,6 +45,7 @@ import { MiNote } from '@/models/Note.js'; import { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { MiNoteReaction } from '@/models/NoteReaction.js'; import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js'; +import { MiNoteDraft } from '@/models/NoteDraft.js'; import { MiPage } from '@/models/Page.js'; import { MiPageLike } from '@/models/PageLike.js'; import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js'; @@ -100,12 +101,6 @@ export type LoggerProps = { printReplicationMode?: boolean, }; -function highlightSql(sql: string) { - return highlight.highlight(sql, { - language: 'sql', ignoreIllegals: true, - }); -} - function truncateSql(sql: string) { return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql; } @@ -131,7 +126,7 @@ class MyCustomLogger implements Logger { modded = truncateSql(modded); } - return highlightSql(modded); + return modded; } @bindThis @@ -211,6 +206,7 @@ export const entities = [ MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, + MiNoteDraft, MiPage, MiPageLike, MiGalleryPost, @@ -238,6 +234,7 @@ export const entities = [ MiChannel, MiChannelFollowing, MiChannelFavorite, + MiChannelMuting, MiRegistryItem, MiAd, MiPasswordResetRequest, diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 9044285bf6..e64882c4df 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -6,11 +6,11 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; -import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QueueProcessorService } from './QueueProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js'; +import { PostScheduledNoteProcessorService } from './processors/PostScheduledNoteProcessorService.js'; import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js'; import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js'; @@ -18,6 +18,8 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; +import { CheckModeratorsActivityProcessorService } from './processors/CheckModeratorsActivityProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; @@ -78,11 +80,13 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor UserWebhookDeliverProcessorService, SystemWebhookDeliverProcessorService, EndedPollNotificationProcessorService, + PostScheduledNoteProcessorService, DeliverProcessorService, InboxProcessorService, AggregateRetentionProcessorService, CheckExpiredMutingsProcessorService, CheckModeratorsActivityProcessorService, + CleanRemoteNotesProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index c98ebcdcd9..2b3b3fc0ad 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -5,7 +5,6 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Bull from 'bullmq'; -import * as Sentry from '@sentry/node'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; @@ -14,6 +13,7 @@ import { CheckModeratorsActivityProcessorService } from '@/queue/processors/Chec import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js'; import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js'; import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js'; +import { PostScheduledNoteProcessorService } from './processors/PostScheduledNoteProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; @@ -43,6 +43,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QUEUE, baseWorkerOptions } from './const.js'; @@ -84,6 +85,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private relationshipQueueWorker: Bull.Worker; private objectStorageQueueWorker: Bull.Worker; private endedPollNotificationQueueWorker: Bull.Worker; + private postScheduledNoteQueueWorker: Bull.Worker; constructor( @Inject(DI.config) @@ -93,6 +95,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private userWebhookDeliverProcessorService: UserWebhookDeliverProcessorService, private systemWebhookDeliverProcessorService: SystemWebhookDeliverProcessorService, private endedPollNotificationProcessorService: EndedPollNotificationProcessorService, + private postScheduledNoteProcessorService: PostScheduledNoteProcessorService, private deliverProcessorService: DeliverProcessorService, private inboxProcessorService: InboxProcessorService, private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService, @@ -123,6 +126,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService, private cleanProcessorService: CleanProcessorService, + private cleanRemoteNotesProcessorService: CleanRemoteNotesProcessorService, ) { this.logger = this.queueLoggerService.logger; @@ -152,6 +156,13 @@ export class QueueProcessorService implements OnApplicationShutdown { }; } + let Sentry: typeof import('@sentry/node') | undefined; + if (this.config.sentryForBackend) { + import('@sentry/node').then((mod) => { + Sentry = mod; + }); + } + //#region system { const processer = (job: Bull.Job) => { @@ -164,12 +175,13 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); + case 'cleanRemoteNotes': return this.cleanRemoteNotesProcessorService.process(job); default: throw new Error(`unrecognized job type ${job.name} for system`); } }; this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: System: ' + job.name }, () => processer(job)); } else { return processer(job); @@ -186,7 +198,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err: Error) => { logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -226,7 +238,7 @@ export class QueueProcessorService implements OnApplicationShutdown { }; this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: DB: ' + job.name }, () => processer(job)); } else { return processer(job); @@ -243,7 +255,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -258,7 +270,7 @@ export class QueueProcessorService implements OnApplicationShutdown { //#region deliver { this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: Deliver' }, () => this.deliverProcessorService.process(job)); } else { return this.deliverProcessorService.process(job); @@ -283,7 +295,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -298,7 +310,7 @@ export class QueueProcessorService implements OnApplicationShutdown { //#region inbox { this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: Inbox' }, () => this.inboxProcessorService.process(job)); } else { return this.inboxProcessorService.process(job); @@ -323,7 +335,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) }); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -338,7 +350,7 @@ export class QueueProcessorService implements OnApplicationShutdown { //#region user-webhook deliver { this.userWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.USER_WEBHOOK_DELIVER, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: UserWebhookDeliver' }, () => this.userWebhookDeliverProcessorService.process(job)); } else { return this.userWebhookDeliverProcessorService.process(job); @@ -363,7 +375,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -378,7 +390,7 @@ export class QueueProcessorService implements OnApplicationShutdown { //#region system-webhook deliver { this.systemWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.SYSTEM_WEBHOOK_DELIVER, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: SystemWebhookDeliver' }, () => this.systemWebhookDeliverProcessorService.process(job)); } else { return this.systemWebhookDeliverProcessorService.process(job); @@ -403,7 +415,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -428,7 +440,7 @@ export class QueueProcessorService implements OnApplicationShutdown { }; this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: Relationship: ' + job.name }, () => processer(job)); } else { return processer(job); @@ -450,7 +462,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -473,7 +485,7 @@ export class QueueProcessorService implements OnApplicationShutdown { }; this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: ObjectStorage: ' + job.name }, () => processer(job)); } else { return processer(job); @@ -491,7 +503,7 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); - if (config.sentryForBackend) { + if (Sentry != null) { Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, @@ -506,7 +518,7 @@ export class QueueProcessorService implements OnApplicationShutdown { //#region ended poll notification { this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => { - if (this.config.sentryForBackend) { + if (Sentry != null) { return Sentry.startSpan({ name: 'Queue: EndedPollNotification' }, () => this.endedPollNotificationProcessorService.process(job)); } else { return this.endedPollNotificationProcessorService.process(job); @@ -517,6 +529,21 @@ export class QueueProcessorService implements OnApplicationShutdown { }); } //#endregion + + //#region post scheduled note + { + this.postScheduledNoteQueueWorker = new Bull.Worker(QUEUE.POST_SCHEDULED_NOTE, async (job) => { + if (Sentry != null) { + return Sentry.startSpan({ name: 'Queue: PostScheduledNote' }, () => this.postScheduledNoteProcessorService.process(job)); + } else { + return this.postScheduledNoteProcessorService.process(job); + } + }, { + ...baseWorkerOptions(this.config, QUEUE.POST_SCHEDULED_NOTE), + autorun: false, + }); + } + //#endregion } @bindThis @@ -531,6 +558,7 @@ export class QueueProcessorService implements OnApplicationShutdown { this.relationshipQueueWorker.run(), this.objectStorageQueueWorker.run(), this.endedPollNotificationQueueWorker.run(), + this.postScheduledNoteQueueWorker.run(), ]); } @@ -546,6 +574,7 @@ export class QueueProcessorService implements OnApplicationShutdown { this.relationshipQueueWorker.close(), this.objectStorageQueueWorker.close(), this.endedPollNotificationQueueWorker.close(), + this.postScheduledNoteQueueWorker.close(), ]); } diff --git a/packages/backend/src/queue/const.ts b/packages/backend/src/queue/const.ts index 7e146a7e03..625204b7ad 100644 --- a/packages/backend/src/queue/const.ts +++ b/packages/backend/src/queue/const.ts @@ -12,6 +12,7 @@ export const QUEUE = { INBOX: 'inbox', SYSTEM: 'system', ENDED_POLL_NOTIFICATION: 'endedPollNotification', + POST_SCHEDULED_NOTE: 'postScheduledNote', DB: 'db', RELATIONSHIP: 'relationship', OBJECT_STORAGE: 'objectStorage', 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/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 728fc9e72b..782b74f0cd 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -34,6 +34,11 @@ export class CleanRemoteFilesProcessorService { let deletedCount = 0; let cursor: MiDriveFile['id'] | null = null; + const total = await this.driveFilesRepository.countBy({ + userHost: Not(IsNull()), + isLink: false, + }); + while (true) { const files = await this.driveFilesRepository.find({ where: { @@ -58,12 +63,7 @@ export class CleanRemoteFilesProcessorService { deletedCount += 8; - const total = await this.driveFilesRepository.countBy({ - userHost: Not(IsNull()), - isLink: false, - }); - - job.updateProgress(100 / total * deletedCount); + job.updateProgress(deletedCount * total / 100); } this.logger.succ('All cached remote files has been deleted.'); diff --git a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts new file mode 100644 index 0000000000..bc99dea000 --- /dev/null +++ b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts @@ -0,0 +1,326 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { setTimeout } from 'node:timers/promises'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, IsNull, LessThan, QueryFailedError, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { MiMeta, MiNote, NotesRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; + +@Injectable() +export class CleanRemoteNotesProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.meta) + private meta: MiMeta, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.db) + private db: DataSource, + + private idService: IdService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes'); + } + + @bindThis + private computeProgress(minId: string, maxId: string, cursorLeft: string) { + const minTs = this.idService.parse(minId).date.getTime(); + const maxTs = this.idService.parse(maxId).date.getTime(); + const cursorTs = this.idService.parse(cursorLeft).date.getTime(); + + return ((cursorTs - minTs) / (maxTs - minTs)) * 100; + } + + @bindThis + public async process(job: Bull.Job>): Promise<{ + deletedCount: number; + oldest: number | null; + newest: number | null; + skipped: boolean; + transientErrors: number; + }> { + const getConfig = () => { + return { + enabled: this.meta.enableRemoteNotesCleaning, + maxDuration: this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000, // Convert minutes to milliseconds + // The date limit for the newest note to be considered for deletion. + // All notes newer than this limit will always be retained. + newestLimit: this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)), + }; + }; + + const initialConfig = getConfig(); + if (!this.meta.enableRemoteNotesCleaning) { + this.logger.info('Remote notes cleaning is disabled, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: true, + transientErrors: 0, + }; + } + + this.logger.info('cleaning remote notes...'); + + const startAt = Date.now(); + + //#region queries + + // The condition for removing the notes. + // The note must be: + // - old enough (older than the newestLimit) + // - a remote note (userHost is not null). + // - not have clipped + // - not have pinned on the user profile + // - not has been favorite by any user + const removalCriteria = [ + 'note."id" < :newestLimit', + 'note."clippedCount" = 0', + 'note."pageCount" = 0', + 'note."userHost" IS NOT NULL', + 'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")', + 'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")', + 'NOT EXISTS (SELECT 1 FROM note_reaction INNER JOIN "user" ON note_reaction."userId" = "user".id WHERE note_reaction."noteId" = note."id" AND "user"."host" IS NULL)', + ].join(' AND '); + + const minId = (await this.notesRepository.createQueryBuilder('note') + .select('MIN(note.id)', 'minId') + .where({ + id: LessThan(initialConfig.newestLimit), + userHost: Not(IsNull()), + replyId: IsNull(), + renoteId: IsNull(), + }) + .getRawOne<{ minId?: MiNote['id'] }>())?.minId; + + if (!minId) { + this.logger.info('No notes can possibly be deleted, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: false, + transientErrors: 0, + }; + } + + // start with a conservative limit and adjust it based on the query duration + const minimumLimit = 10; + let currentLimit = 100; + let cursorLeft = '0'; + + const candidateNotesCteName = 'candidate_notes'; + + // tree walk down all root notes, short-circuit when the first unremovable note is found + const candidateNotesQueryBase = this.notesRepository.createQueryBuilder('note') + .select('note."id"', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('note."id"', 'rootId') + .addSelect('TRUE', 'isRemovable') + .addSelect('TRUE', 'isBase') + .where('note."id" > :cursorLeft') + .andWhere(removalCriteria) + .andWhere({ replyId: IsNull(), renoteId: IsNull() }); + + const candidateNotesQueryInductive = this.notesRepository.createQueryBuilder('note') + .select('note.id', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('parent."rootId"', 'rootId') + .addSelect(removalCriteria, 'isRemovable') + .addSelect('FALSE', 'isBase') + .innerJoin(candidateNotesCteName, 'parent', 'parent."id" = note."replyId" OR parent."id" = note."renoteId"') + .where('parent."isRemovable" = TRUE'); + + // A note tree can be deleted if there are no unremovable rows with the same rootId. + // + // `candidate_notes` will have the following structure after recursive query (some columns omitted): + // After performing a LEFT JOIN with `candidate_notes` as `unremovable`, + // the note tree containing unremovable notes will be anti-joined. + // For removable rows, the `unremovable` columns will have `NULL` values. + // | id | rootId | isRemovable | + // |-----|--------|-------------| + // | aaa | aaa | TRUE | + // | bbb | aaa | FALSE | + // | ccc | aaa | FALSE | + // | ddd | ddd | TRUE | + // | eee | ddd | TRUE | + // | fff | fff | TRUE | + // | ggg | ggg | FALSE | + // + const candidateNotesQuery = ({ limit }: { limit: number }) => this.db.createQueryBuilder() + .select(`"${candidateNotesCteName}"."id"`, 'id') + .addSelect('unremovable."id" IS NULL', 'isRemovable') + .addSelect(`BOOL_OR("${candidateNotesCteName}"."isBase")`, 'isBase') + .addCommonTableExpression( + `((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(limit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`, + candidateNotesCteName, + { recursive: true }, + ) + .from(candidateNotesCteName, candidateNotesCteName) + .leftJoin(candidateNotesCteName, 'unremovable', `unremovable."rootId" = "${candidateNotesCteName}"."rootId" AND unremovable."isRemovable" = FALSE`) + .groupBy(`"${candidateNotesCteName}"."id"`) + .addGroupBy('unremovable."id" IS NULL'); + + const stats = { + deletedCount: 0, + oldest: null as number | null, + newest: null as number | null, + }; + + let lowThroughputWarned = false; + let transientErrors = 0; + for (;;) { + const { enabled, maxDuration, newestLimit } = getConfig(); + if (!enabled) { + this.logger.info('Remote notes cleaning is disabled, processing stopped...'); + break; + } + //#region check time + const batchBeginAt = Date.now(); + + const elapsed = batchBeginAt - startAt; + + const progress = this.computeProgress(minId, newestLimit, cursorLeft > minId ? cursorLeft : minId); + + if (elapsed >= maxDuration) { + job.log(`Reached maximum duration of ${maxDuration}ms, stopping... (last cursor: ${cursorLeft}, final progress ${progress}%)`); + job.updateProgress(100); + break; + } + + const wallClockUsage = elapsed / maxDuration; + if (wallClockUsage > 0.5 && progress < 50 && !lowThroughputWarned) { + const msg = `Not projected to finish in time! (wall clock usage ${wallClockUsage * 100}% at ${progress}%, current limit ${currentLimit})`; + this.logger.warn(msg); + job.log(msg); + lowThroughputWarned = true; + } + job.updateProgress(progress); + //#endregion + + const queryBegin = performance.now(); + let noteIds = null; + + try { + noteIds = await candidateNotesQuery({ limit: currentLimit }).setParameters( + { newestLimit, cursorLeft }, + ).getRawMany<{ id: MiNote['id'], isRemovable: boolean, isBase: boolean }>(); + } catch (e) { + if (e instanceof QueryFailedError && e.driverError?.code === '57014') { + // Statement timeout (maybe suddenly hit a large note tree), if possible, reduce the limit and try again + // if not possible, skip the current batch of notes and find the next root note + if (currentLimit <= minimumLimit) { + job.log('Local note tree complexity is too high, finding next root note...'); + + const idWindow = await this.notesRepository.createQueryBuilder('note') + .select('id') + .where('note.id > :cursorLeft') + .andWhere(removalCriteria) + .andWhere({ replyId: IsNull(), renoteId: IsNull() }) + .orderBy('note.id', 'ASC') + .limit(minimumLimit + 1) + .setParameters({ cursorLeft, newestLimit }) + .getRawMany<{ id?: MiNote['id'] }>(); + + job.log(`Skipped note IDs: ${idWindow.slice(0, minimumLimit).map(id => id.id).join(', ')}`); + + const lastId = idWindow.at(minimumLimit)?.id; + + if (!lastId) { + job.log('No more notes to clean.'); + break; + } + + cursorLeft = lastId; + continue; + } + currentLimit = Math.max(minimumLimit, Math.floor(currentLimit * 0.25)); + continue; + } + throw e; + } + + if (noteIds.length === 0) { + job.log('No more notes to clean.'); + break; + } + + const queryDuration = performance.now() - queryBegin; + // try to adjust such that each query takes about 1~5 seconds and reasonable NodeJS heap so the task stays responsive + // this should not oscillate.. + if (queryDuration > 5000 || noteIds.length > 5000) { + currentLimit = Math.floor(currentLimit * 0.5); + } else if (queryDuration < 1000 && noteIds.length < 1000) { + currentLimit = Math.floor(currentLimit * 1.5); + } + // clamp to a sane range + currentLimit = Math.min(Math.max(currentLimit, minimumLimit), 5000); + + const deletableNoteIds = noteIds.filter(result => result.isRemovable).map(result => result.id); + if (deletableNoteIds.length > 0) { + try { + await this.notesRepository.delete(deletableNoteIds); + + for (const id of deletableNoteIds) { + const t = this.idService.parse(id).date.getTime(); + if (stats.oldest === null || t < stats.oldest) { + stats.oldest = t; + } + if (stats.newest === null || t > stats.newest) { + stats.newest = t; + } + } + + stats.deletedCount += deletableNoteIds.length; + } catch (e) { + // check for integrity violation errors (class 23) that might have occurred between the check and the delete + // we can safely continue to the next batch + if (e instanceof QueryFailedError && e.driverError?.code?.startsWith('23')) { + transientErrors++; + job.log(`Error deleting notes: ${e} (transient race condition?)`); + } else { + throw e; + } + } + } + + cursorLeft = noteIds.filter(result => result.isBase).reduce((max, { id }) => id > max ? id : max, cursorLeft); + + job.log(`Deleted ${noteIds.length} notes; ${Date.now() - batchBeginAt}ms`); + + if (process.env.NODE_ENV !== 'test') { + await setTimeout(Math.min(1000 * 5, queryDuration)); // Wait a moment to avoid overwhelming the db + } + }; + + if (transientErrors > 0) { + const msg = `${transientErrors} transient errors occurred while cleaning remote notes. You may need a second pass to complete the cleaning.`; + this.logger.warn(msg); + job.log(msg); + } + this.logger.succ('cleaning of remote notes completed.'); + + return { + deletedCount: stats.deletedCount, + oldest: stats.oldest, + newest: stats.newest, + skipped: false, + transientErrors, + }; + } +} diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 14a53e0c42..b643c2a6d0 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { DriveFilesRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; @@ -14,6 +14,7 @@ import type { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { PageService } from '@/core/PageService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; @@ -35,7 +36,11 @@ export class DeleteAccountProcessorService { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + private driveService: DriveService, + private pageService: PageService, private emailService: EmailService, private queueLoggerService: QueueLoggerService, private searchService: SearchService, @@ -112,6 +117,28 @@ export class DeleteAccountProcessorService { this.logger.succ('All of files deleted'); } + { + // delete pages. Necessary for decrementing pageCount of notes. + while (true) { + const pages = await this.pagesRepository.find({ + where: { + userId: user.id, + }, + take: 100, + order: { + id: 1, + }, + }); + + if (pages.length === 0) { + break; + } + for (const page of pages) { + await this.pageService.delete(user, page.id); + } + } + } + { // Send email notification const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 291fa4a6d8..5a8a8ca940 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -43,6 +43,10 @@ export class DeleteDriveFilesProcessorService { let deletedCount = 0; let cursor: MiDriveFile['id'] | null = null; + const total = await this.driveFilesRepository.countBy({ + userId: user.id, + }); + while (true) { const files = await this.driveFilesRepository.find({ where: { @@ -67,11 +71,7 @@ export class DeleteDriveFilesProcessorService { deletedCount++; } - const total = await this.driveFilesRepository.countBy({ - userId: user.id, - }); - - job.updateProgress(deletedCount / total); + job.updateProgress(deletedCount / total * 100); } this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 5a16496011..391ccdac05 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -71,6 +71,15 @@ export class DeliverProcessorService { return 'skip (suspended)'; } + const i = await (this.meta.enableStatsForFederatedInstances + ? this.federatedInstanceService.fetchOrRegister(host) + : this.federatedInstanceService.fetch(host)); + + // suspend server by software + if (i != null && this.utilityService.isDeliverSuspendedSoftware(i)) { + return 'skip (software suspended)'; + } + try { await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest); @@ -79,10 +88,6 @@ export class DeliverProcessorService { // Update instance stats process.nextTick(async () => { - const i = await (this.meta.enableStatsForFederatedInstances - ? this.federatedInstanceService.fetchOrRegister(host) - : this.federatedInstanceService.fetch(host)); - if (i == null) return; if (i.isNotResponding) { diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index b3111865ad..053ba99005 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -15,6 +15,7 @@ import { bindThis } from '@/decorators.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { ExportedAntenna } from '@/queue/processors/ImportAntennasProcessorService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -86,7 +87,8 @@ export class ExportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, - })); + excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel, + } satisfies Required)); if (antennas.length - 1 !== index) { write(', '); } diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index ecc439db69..cca7cdf9da 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -58,6 +58,10 @@ export class ExportBlockingProcessorService { let exportedCount = 0; let cursor: MiBlocking['id'] | null = null; + const total = await this.blockingsRepository.countBy({ + blockerId: user.id, + }); + while (true) { const blockings = await this.blockingsRepository.find({ where: { @@ -97,11 +101,7 @@ export class ExportBlockingProcessorService { exportedCount++; } - const total = await this.blockingsRepository.countBy({ - blockerId: user.id, - }); - - job.updateProgress(exportedCount / total); + job.updateProgress(exportedCount / total * 100); } stream.end(); diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts index 583ddbb745..be7d4e9e21 100644 --- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -5,21 +5,20 @@ import * as fs from 'node:fs'; import { Writable } from 'node:stream'; -import { Inject, Injectable, StreamableFile } from '@nestjs/common'; -import { MoreThan } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; +import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, PollsRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import type { MiPoll } from '@/models/Poll.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { QueryService } from '@/core/QueryService.js'; +import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -43,6 +42,7 @@ export class ExportClipsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private queryService: QueryService, private idService: IdService, private notificationService: NotificationService, ) { @@ -95,17 +95,21 @@ export class ExportClipsProcessorService { let exportedClipsCount = 0; let cursor: MiClip['id'] | null = null; + const total = await this.clipsRepository.countBy({ + userId: user.id, + }); + while (true) { - const clips = await this.clipsRepository.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }); + const query = this.clipsRepository.createQueryBuilder('clip') + .where('clip.userId = :userId', { userId: user.id }) + .orderBy('clip.id', 'ASC') + .take(100); + + if (cursor) { + query.andWhere('clip.id > :cursor', { cursor }); + } + + const clips = await query.getMany(); if (clips.length === 0) { job.updateProgress(100); @@ -120,36 +124,35 @@ export class ExportClipsProcessorService { const isFirst = exportedClipsCount === 0; await writer.write(isFirst ? content : ',\n' + content); - await this.processClipNotes(writer, clip.id); + await this.processClipNotes(writer, clip.id, user.id); await writer.write(']}'); exportedClipsCount++; } - const total = await this.clipsRepository.countBy({ - userId: user.id, - }); - - job.updateProgress(exportedClipsCount / total); + job.updateProgress(exportedClipsCount / total * 100); } } - async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise { + async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string, userId: string): Promise { let exportedClipNotesCount = 0; let cursor: MiClipNote['id'] | null = null; while (true) { - const clipNotes = await this.clipNotesRepository.find({ - where: { - clipId, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - relations: ['note', 'note.user'], - }) as (MiClipNote & { note: MiNote & { user: MiUser } })[]; + const query = this.clipNotesRepository.createQueryBuilder('clipNote') + .leftJoinAndSelect('clipNote.note', 'note') + .leftJoinAndSelect('note.user', 'user') + .where('clipNote.clipId = :clipId', { clipId }) + .orderBy('clipNote.id', 'ASC') + .take(100); + + if (cursor) { + query.andWhere('clipNote.id > :cursor', { cursor }); + } + + this.queryService.generateVisibilityQuery(query, { id: userId }); + + const clipNotes = await query.getMany() as (MiClipNote & { note: MiNote & { user: MiUser } })[]; if (clipNotes.length === 0) { break; @@ -158,6 +161,11 @@ export class ExportClipsProcessorService { cursor = clipNotes.at(-1)?.id ?? null; for (const clipNote of clipNotes) { + const noteCreatedAt = this.idService.parse(clipNote.note.id).date; + if (shouldHideNoteByTime(clipNote.note.user.makeNotesHiddenBefore, noteCreatedAt)) { + continue; + } + let poll: MiPoll | undefined; if (clipNote.note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id }); diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index e237cd4975..53ecd2d180 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -123,8 +123,8 @@ export class ExportCustomEmojisProcessorService { metaStream.end(); // Create archive - await new Promise(async (resolve) => { - const [archivePath, archiveCleanup] = await createTemp(); + const [archivePath, archiveCleanup] = await createTemp(); + await new Promise((resolve) => { const archiveStream = fs.createWriteStream(archivePath); const archive = archiver('zip', { zlib: { level: 0 }, diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index b81feece01..87a8ded307 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -5,7 +5,6 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; -import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; import type { MiNoteFavorite, NoteFavoritesRepository, PollsRepository, MiUser, UsersRepository } from '@/models/_.js'; @@ -17,6 +16,8 @@ import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { QueryService } from '@/core/QueryService.js'; +import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private queryService: QueryService, private idService: IdService, private notificationService: NotificationService, ) { @@ -78,18 +80,25 @@ export class ExportFavoritesProcessorService { let exportedFavoritesCount = 0; let cursor: MiNoteFavorite['id'] | null = null; + const total = await this.noteFavoritesRepository.countBy({ + userId: user.id, + }); + while (true) { - const favorites = await this.noteFavoritesRepository.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - relations: ['note', 'note.user'], - }) as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[]; + const query = this.noteFavoritesRepository.createQueryBuilder('favorite') + .leftJoinAndSelect('favorite.note', 'note') + .leftJoinAndSelect('note.user', 'user') + .where('favorite.userId = :userId', { userId: user.id }) + .orderBy('favorite.id', 'ASC') + .take(100); + + if (cursor) { + query.andWhere('favorite.id > :cursor', { cursor }); + } + + this.queryService.generateVisibilityQuery(query, { id: user.id }); + + const favorites = await query.getMany() as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[]; if (favorites.length === 0) { job.updateProgress(100); @@ -99,6 +108,11 @@ export class ExportFavoritesProcessorService { cursor = favorites.at(-1)?.id ?? null; for (const favorite of favorites) { + const noteCreatedAt = this.idService.parse(favorite.note.id).date; + if (shouldHideNoteByTime(favorite.note.user.makeNotesHiddenBefore, noteCreatedAt)) { + continue; + } + let poll: MiPoll | undefined; if (favorite.note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id }); @@ -109,11 +123,7 @@ export class ExportFavoritesProcessorService { exportedFavoritesCount++; } - const total = await this.noteFavoritesRepository.countBy({ - userId: user.id, - }); - - job.updateProgress(exportedFavoritesCount / total); + job.updateProgress(exportedFavoritesCount / total * 100); } await write(']'); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 903f962515..91c39cb758 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -94,7 +94,8 @@ export class ExportFollowingProcessorService { continue; } - const content = this.utilityService.getFullApAccount(u.username, u.host); + const userAcct = this.utilityService.getFullApAccount(u.username, u.host); + const content = `${userAcct},withReplies=${following.withReplies}`; await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index f9867ade29..59448ccd34 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -58,6 +58,10 @@ export class ExportMutingProcessorService { let exportedCount = 0; let cursor: MiMuting['id'] | null = null; + const total = await this.mutingsRepository.countBy({ + muterId: user.id, + }); + while (true) { const mutes = await this.mutingsRepository.find({ where: { @@ -98,11 +102,7 @@ export class ExportMutingProcessorService { exportedCount++; } - const total = await this.mutingsRepository.countBy({ - muterId: user.id, - }); - - job.updateProgress(exportedCount / total); + job.updateProgress(exportedCount / total * 100); } stream.end(); diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 9e2b678219..3b68a4277a 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -37,6 +37,8 @@ class NoteStream extends ReadableStream> { let exportedNotesCount = 0; let cursor: MiNote['id'] | null = null; + const totalPromise = notesRepository.countBy({ userId }); + const serialize = ( note: MiNote, poll: MiPoll | null, @@ -88,8 +90,8 @@ class NoteStream extends ReadableStream> { exportedNotesCount++; } - const total = await notesRepository.countBy({ userId }); - job.updateProgress(exportedNotesCount / total); + const total = await totalPromise; + job.updateProgress(exportedNotesCount / total * 100); }, }); } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index c483d79854..733e75f65f 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -67,10 +67,12 @@ export class ExportUserListsProcessorService { const users = await this.usersRepository.findBy({ id: In(memberships.map(j => j.userId)), }); + const usersWithReplies = new Set(memberships.filter(m => m.withReplies).map(m => m.userId)); for (const u of users) { const acct = this.utilityService.getFullApAccount(u.username, u.host); - const content = `${list.name},${acct}`; + // 3rd column and later will be key=value pairs + const content = `${list.name},${acct},withReplies=${usersWithReplies.has(u.id)}`; await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 9c033b73e2..4c7f2d09bb 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -11,17 +11,18 @@ import Logger from '@/logger.js'; import type { AntennasRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { Schema, SchemaType } from '@/misc/json-schema.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { DBAntennaImportJobData } from '../types.js'; import type * as Bull from 'bullmq'; const Ajv = _Ajv.default; -const validate = new Ajv().compile({ +const exportedAntennaSchema = { type: 'object', properties: { name: { type: 'string', minLength: 1, maxLength: 100 }, - src: { type: 'string', enum: ['home', 'all', 'users', 'list'] }, + src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'users_blacklist'] }, userListAccts: { type: 'array', items: { @@ -47,9 +48,14 @@ const validate = new Ajv().compile({ excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, + excludeNotesInSensitiveChannel: { type: 'boolean' }, }, required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], -}); +} as const satisfies Schema; + +export type ExportedAntenna = SchemaType; + +const validate = new Ajv().compile(exportedAntennaSchema); @Injectable() export class ImportAntennasProcessorService { @@ -91,6 +97,7 @@ export class ImportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, + excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel, }); this.logger.succ('Antenna created: ' + result.id); this.globalEventService.publishInternalEvent('antennaCreated', result); diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 70c9f3a096..03663d3b06 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -67,8 +67,19 @@ export class ImportFollowingProcessorService { const user = job.data.user; try { - const acct = line.split(',')[0].trim(); + const parts = line.split(','); + const acct = parts[0].trim(); const { username, host } = Acct.parse(acct); + let withReplies: boolean | null = null; + + for (const keyValue of parts.slice(2)) { + const [key, value] = keyValue.split('='); + switch (key) { + case 'withReplies': + withReplies = value === 'true'; + break; + } + } if (!host) return; @@ -95,7 +106,7 @@ export class ImportFollowingProcessorService { this.logger.info(`Follow ${target.id} ${job.data.withReplies ? 'with replies' : 'without replies'} ...`); - this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]); + await this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: withReplies ?? job.data.withReplies }]); } catch (e) { this.logger.warn(`Error: ${e}`); } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index db9255b35d..bf061a1f78 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -70,8 +70,19 @@ export class ImportUserListsProcessorService { linenum++; try { - const listName = line.split(',')[0].trim(); - const { username, host } = Acct.parse(line.split(',')[1].trim()); + const parts = line.split(','); + const listName = parts[0].trim(); + const { username, host } = Acct.parse(parts[1].trim()); + let withReplies = false; + + for (const keyValue of parts.slice(2)) { + const [key, value] = keyValue.split('='); + switch (key) { + case 'withReplies': + withReplies = value === 'true'; + break; + } + } let list = await this.userListsRepository.findOneBy({ userId: user.id, @@ -100,7 +111,9 @@ export class ImportUserListsProcessorService { if (await this.userListMembershipsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; - this.userListService.addMember(target, list!, user); + await this.userListService.addMember(target, list, user, { + withReplies: withReplies, + }); } catch (e) { this.logger.warn(`Error in line:${linenum} ${e}`); } diff --git a/packages/backend/src/queue/processors/PostScheduledNoteProcessorService.ts b/packages/backend/src/queue/processors/PostScheduledNoteProcessorService.ts new file mode 100644 index 0000000000..719a09980c --- /dev/null +++ b/packages/backend/src/queue/processors/PostScheduledNoteProcessorService.ts @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NoteDraftsRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { NotificationService } from '@/core/NotificationService.js'; +import { bindThis } from '@/decorators.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { PostScheduledNoteJobData } from '../types.js'; + +@Injectable() +export class PostScheduledNoteProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.noteDraftsRepository) + private noteDraftsRepository: NoteDraftsRepository, + + private noteCreateService: NoteCreateService, + private notificationService: NotificationService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('post-scheduled-note'); + } + + @bindThis + public async process(job: Bull.Job): Promise { + const draft = await this.noteDraftsRepository.findOne({ where: { id: job.data.noteDraftId }, relations: ['user'] }); + if (draft == null || draft.user == null || draft.scheduledAt == null || !draft.isActuallyScheduled) { + return; + } + + try { + const note = await this.noteCreateService.fetchAndCreate(draft.user, { + createdAt: new Date(), + fileIds: draft.fileIds, + poll: draft.hasPoll ? { + choices: draft.pollChoices, + multiple: draft.pollMultiple, + expiresAt: draft.pollExpiredAfter ? new Date(Date.now() + draft.pollExpiredAfter) : draft.pollExpiresAt ? new Date(draft.pollExpiresAt) : null, + } : null, + text: draft.text ?? null, + replyId: draft.replyId, + renoteId: draft.renoteId, + cw: draft.cw, + localOnly: draft.localOnly, + reactionAcceptance: draft.reactionAcceptance, + visibility: draft.visibility, + visibleUserIds: draft.visibleUserIds, + channelId: draft.channelId, + }); + + // await不要 + this.noteDraftsRepository.remove(draft); + + // await不要 + this.notificationService.createNotification(draft.userId, 'scheduledNotePosted', { + noteId: note.id, + }); + } catch (_) { + this.notificationService.createNotification(draft.userId, 'scheduledNotePostFailed', { + noteDraftId: draft.id, + }); + } + } +} diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 757daea88b..1cb2b93918 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -109,6 +109,10 @@ export type EndedPollNotificationJobData = { noteId: MiNote['id']; }; +export type PostScheduledNoteJobData = { + noteDraftId: string; +}; + export type SystemWebhookDeliverJobData = { type: T; content: SystemWebhookPayload; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index f7b22c44c4..54ffeecc6b 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -116,7 +116,7 @@ export class ActivityPubServerService { try { signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'host', 'date'], authorizationHeaderName: 'signature' }); - } catch (e) { + } catch (_) { reply.code(401); return; } @@ -482,9 +482,19 @@ export class ActivityPubServerService { return true; }, dbFallback: async (untilId, sinceId, limit) => { - return await this.getUserNotesFromDb(sinceId, untilId, limit, user.id); + return await this.getUserNotesFromDb({ + untilId, + sinceId, + limit, + userId: user.id, + }); }, - }) : await this.getUserNotesFromDb(sinceId ?? null, untilId ?? null, limit, user.id); + }) : await this.getUserNotesFromDb({ + untilId: untilId ?? null, + sinceId: sinceId ?? null, + limit, + userId: user.id, + }); if (sinceId) notes.reverse(); @@ -523,16 +533,21 @@ export class ActivityPubServerService { } @bindThis - private async getUserNotesFromDb(untilId: string | null, sinceId: string | null, limit: number, userId: MiUser['id']) { - return await this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId) - .andWhere('note.userId = :userId', { userId }) + private async getUserNotesFromDb(ps: { + untilId: string | null, + sinceId: string | null, + limit: number, + userId: MiUser['id'], + }) { + return await this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.userId = :userId', { userId: ps.userId }) .andWhere(new Brackets(qb => { qb .where('note.visibility = \'public\'') .orWhere('note.visibility = \'home\''); })) .andWhere('note.localOnly = FALSE') - .limit(limit) + .limit(ps.limit) .getMany(); } diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 772c37094c..f5034d0733 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -7,27 +7,22 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; -import rename from 'rename'; -import sharp from 'sharp'; -import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import type { Config } from '@/config.js'; -import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import { StatusError } from '@/misc/status-error.js'; import type Logger from '@/logger.js'; import { DownloadService } from '@/core/DownloadService.js'; -import { IImageStreamable, ImageProcessingService, webpDefault } from '@/core/ImageProcessingService.js'; -import { VideoProcessingService } from '@/core/VideoProcessingService.js'; import { InternalStorageService } from '@/core/InternalStorageService.js'; -import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; +import { ImageProcessingService } from '@/core/ImageProcessingService.js'; +import { VideoProcessingService } from '@/core/VideoProcessingService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; -import { isMimeImage } from '@/misc/is-mime-image.js'; -import { correctFilename } from '@/misc/correct-filename.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; +import { FileServerDriveHandler } from './file/FileServerDriveHandler.js'; +import { FileServerFileResolver } from './file/FileServerFileResolver.js'; +import { FileServerProxyHandler } from './file/FileServerProxyHandler.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; const _filename = fileURLToPath(import.meta.url); @@ -38,6 +33,9 @@ const assets = `${_dirname}/../../server/file/assets/`; @Injectable() export class FileServerService { private logger: Logger; + private driveHandler: FileServerDriveHandler; + private proxyHandler: FileServerProxyHandler; + private fileResolver: FileServerFileResolver; constructor( @Inject(DI.config) @@ -54,6 +52,24 @@ export class FileServerService { private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('server', 'gray'); + this.fileResolver = new FileServerFileResolver( + this.driveFilesRepository, + this.fileInfoService, + this.downloadService, + this.internalStorageService, + ); + this.driveHandler = new FileServerDriveHandler( + this.config, + this.fileResolver, + assets, + this.videoProcessingService, + ); + this.proxyHandler = new FileServerProxyHandler( + this.config, + this.fileResolver, + assets, + this.imageProcessingService, + ); //this.createServer = this.createServer.bind(this); } @@ -78,7 +94,7 @@ export class FileServerService { }); fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => { - return await this.sendDriveFile(request, reply) + return await this.driveHandler.handle(request, reply) .catch(err => this.errorHandler(request, reply, err)); }); fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { @@ -91,7 +107,7 @@ export class FileServerService { Params: { url: string; }; Querystring: { url?: string; }; }>('/proxy/:url*', async (request, reply) => { - return await this.proxyHandler(request, reply) + return await this.proxyHandler.handle(request, reply) .catch(err => this.errorHandler(request, reply, err)); }); @@ -116,462 +132,4 @@ export class FileServerService { reply.code(500); return; } - - @bindThis - private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) { - const key = request.params.key; - const file = await this.getFileFromKey(key).then(); - - if (file === '404') { - reply.code(404); - reply.header('Cache-Control', 'max-age=86400'); - return reply.sendFile('/dummy.png', assets); - } - - if (file === '204') { - reply.code(204); - reply.header('Cache-Control', 'max-age=86400'); - return; - } - - try { - if (file.state === 'remote') { - let image: IImageStreamable | null = null; - - if (file.fileRole === 'thumbnail') { - if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { - reply.header('Cache-Control', 'max-age=31536000, immutable'); - - const url = new URL(`${this.config.mediaProxy}/static.webp`); - url.searchParams.set('url', file.url); - url.searchParams.set('static', '1'); - - file.cleanup(); - return await reply.redirect(url.toString(), 301); - } else if (file.mime.startsWith('video/')) { - const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url); - if (externalThumbnail) { - file.cleanup(); - return await reply.redirect(externalThumbnail, 301); - } - - image = await this.videoProcessingService.generateVideoThumbnail(file.path); - } - } - - if (file.fileRole === 'webpublic') { - if (['image/svg+xml'].includes(file.mime)) { - reply.header('Cache-Control', 'max-age=31536000, immutable'); - - const url = new URL(`${this.config.mediaProxy}/svg.webp`); - url.searchParams.set('url', file.url); - - file.cleanup(); - return await reply.redirect(url.toString(), 301); - } - } - - if (!image) { - if (request.headers.range && file.file.size > 0) { - const range = request.headers.range as string; - const parts = range.replace(/bytes=/, '').split('-'); - const start = parseInt(parts[0], 10); - let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; - if (end > file.file.size) { - end = file.file.size - 1; - } - const chunksize = end - start + 1; - - image = { - data: fs.createReadStream(file.path, { - start, - end, - }), - ext: file.ext, - type: file.mime, - }; - - reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); - reply.header('Accept-Ranges', 'bytes'); - reply.header('Content-Length', chunksize); - reply.code(206); - } else { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; - } - } - - if ('pipe' in image.data && typeof image.data.pipe === 'function') { - // image.dataがstreamなら、stream終了後にcleanup - image.data.on('end', file.cleanup); - image.data.on('close', file.cleanup); - } else { - // image.dataがstreamでないなら直ちにcleanup - file.cleanup(); - } - - reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); - reply.header('Content-Length', file.file.size); - reply.header('Cache-Control', 'max-age=31536000, immutable'); - reply.header('Content-Disposition', - contentDisposition( - 'inline', - correctFilename(file.filename, image.ext), - ), - ); - return image.data; - } - - if (file.fileRole !== 'original') { - const filename = rename(file.filename, { - suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web', - extname: file.ext ? `.${file.ext}` : '.unknown', - }).toString(); - - reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream'); - reply.header('Cache-Control', 'max-age=31536000, immutable'); - reply.header('Content-Disposition', contentDisposition('inline', filename)); - - if (request.headers.range && file.file.size > 0) { - const range = request.headers.range as string; - const parts = range.replace(/bytes=/, '').split('-'); - const start = parseInt(parts[0], 10); - let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; - if (end > file.file.size) { - end = file.file.size - 1; - } - const chunksize = end - start + 1; - const fileStream = fs.createReadStream(file.path, { - start, - end, - }); - reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); - reply.header('Accept-Ranges', 'bytes'); - reply.header('Content-Length', chunksize); - reply.code(206); - return fileStream; - } - - return fs.createReadStream(file.path); - } else { - reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream'); - reply.header('Content-Length', file.file.size); - reply.header('Cache-Control', 'max-age=31536000, immutable'); - reply.header('Content-Disposition', contentDisposition('inline', file.filename)); - - if (request.headers.range && file.file.size > 0) { - const range = request.headers.range as string; - const parts = range.replace(/bytes=/, '').split('-'); - const start = parseInt(parts[0], 10); - let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; - if (end > file.file.size) { - end = file.file.size - 1; - } - const chunksize = end - start + 1; - const fileStream = fs.createReadStream(file.path, { - start, - end, - }); - reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); - reply.header('Accept-Ranges', 'bytes'); - reply.header('Content-Length', chunksize); - reply.code(206); - return fileStream; - } - - return fs.createReadStream(file.path); - } - } catch (e) { - if ('cleanup' in file) file.cleanup(); - throw e; - } - } - - @bindThis - private async proxyHandler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) { - const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url; - - if (typeof url !== 'string') { - reply.code(400); - return; - } - - // アバタークロップなど、どうしてもオリジンである必要がある場合 - const mustOrigin = 'origin' in request.query; - - if (this.config.externalMediaProxyEnabled && !mustOrigin) { - // 外部のメディアプロキシが有効なら、そちらにリダイレクト - - reply.header('Cache-Control', 'public, max-age=259200'); // 3 days - - const url = new URL(`${this.config.mediaProxy}/${request.params.url || ''}`); - - for (const [key, value] of Object.entries(request.query)) { - url.searchParams.append(key, value); - } - - return await reply.redirect( - url.toString(), - 301, - ); - } - - if (!request.headers['user-agent']) { - throw new StatusError('User-Agent is required', 400, 'User-Agent is required'); - } else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) { - throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive'); - } - - // Create temp file - const file = await this.getStreamAndTypeFromUrl(url); - if (file === '404') { - reply.code(404); - reply.header('Cache-Control', 'max-age=86400'); - return reply.sendFile('/dummy.png', assets); - } - - if (file === '204') { - reply.code(204); - reply.header('Cache-Control', 'max-age=86400'); - return; - } - - try { - const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image-with-bmp'); - const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp'); - - if ( - 'emoji' in request.query || - 'avatar' in request.query || - 'static' in request.query || - 'preview' in request.query || - 'badge' in request.query - ) { - if (!isConvertibleImage) { - // 画像でないなら404でお茶を濁す - throw new StatusError('Unexpected mime', 404); - } - } - - let image: IImageStreamable | null = null; - if ('emoji' in request.query || 'avatar' in request.query) { - if (!isAnimationConvertibleImage && !('static' in request.query)) { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; - } else { - const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) })) - .resize({ - height: 'emoji' in request.query ? 128 : 320, - withoutEnlargement: true, - }) - .webp(webpDefault); - - image = { - data, - ext: 'webp', - type: 'image/webp', - }; - } - } else if ('static' in request.query) { - image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422); - } else if ('preview' in request.query) { - image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200); - } else if ('badge' in request.query) { - const mask = (await sharpBmp(file.path, file.mime)) - .resize(96, 96, { - fit: 'contain', - position: 'centre', - withoutEnlargement: false, - }) - .greyscale() - .normalise() - .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast - .flatten({ background: '#000' }) - .toColorspace('b-w'); - - const stats = await mask.clone().stats(); - - if (stats.entropy < 0.1) { - // エントロピーがあまりない場合は404にする - throw new StatusError('Skip to provide badge', 404); - } - - const data = sharp({ - create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, - }) - .pipelineColorspace('b-w') - .boolean(await mask.png().toBuffer(), 'eor'); - - image = { - data: await data.png().toBuffer(), - ext: 'png', - type: 'image/png', - }; - } else if (file.mime === 'image/svg+xml') { - image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048); - } else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) { - throw new StatusError('Rejected type', 403, 'Rejected type'); - } - - if (!image) { - if (request.headers.range && file.file && file.file.size > 0) { - const range = request.headers.range as string; - const parts = range.replace(/bytes=/, '').split('-'); - const start = parseInt(parts[0], 10); - let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; - if (end > file.file.size) { - end = file.file.size - 1; - } - const chunksize = end - start + 1; - - image = { - data: fs.createReadStream(file.path, { - start, - end, - }), - ext: file.ext, - type: file.mime, - }; - - reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); - reply.header('Accept-Ranges', 'bytes'); - reply.header('Content-Length', chunksize); - reply.code(206); - } else { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; - } - } - - if ('cleanup' in file) { - if ('pipe' in image.data && typeof image.data.pipe === 'function') { - // image.dataがstreamなら、stream終了後にcleanup - image.data.on('end', file.cleanup); - image.data.on('close', file.cleanup); - } else { - // image.dataがstreamでないなら直ちにcleanup - file.cleanup(); - } - } - - reply.header('Content-Type', image.type); - reply.header('Cache-Control', 'max-age=31536000, immutable'); - reply.header('Content-Disposition', - contentDisposition( - 'inline', - correctFilename(file.filename, image.ext), - ), - ); - return image.data; - } catch (e) { - if ('cleanup' in file) file.cleanup(); - throw e; - } - } - - @bindThis - private async getStreamAndTypeFromUrl(url: string): Promise< - { state: 'remote'; fileRole?: 'thumbnail' | 'webpublic' | 'original'; file?: MiDriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; } - | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; } - | '404' - | '204' - > { - if (url.startsWith(`${this.config.url}/files/`)) { - const key = url.replace(`${this.config.url}/files/`, '').split('/').shift(); - if (!key) throw new StatusError('Invalid File Key', 400, 'Invalid File Key'); - - return await this.getFileFromKey(key); - } - - return await this.downloadAndDetectTypeFromUrl(url); - } - - @bindThis - private async downloadAndDetectTypeFromUrl(url: string): Promise< - { state: 'remote'; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; } - > { - const [path, cleanup] = await createTemp(); - try { - const { filename } = await this.downloadService.downloadUrl(url, path); - - const { mime, ext } = await this.fileInfoService.detectType(path); - - return { - state: 'remote', - mime, ext, - path, cleanup, - filename, - }; - } catch (e) { - cleanup(); - throw e; - } - } - - @bindThis - private async getFileFromKey(key: string): Promise< - { state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; } - | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; } - | '404' - | '204' - > { - // Fetch drive file - const file = await this.driveFilesRepository.createQueryBuilder('file') - .where('file.accessKey = :accessKey', { accessKey: key }) - .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) - .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) - .getOne(); - - if (file == null) return '404'; - - const isThumbnail = file.thumbnailAccessKey === key; - const isWebpublic = file.webpublicAccessKey === key; - - if (!file.storedInternal) { - if (!(file.isLink && file.uri)) return '204'; - const result = await this.downloadAndDetectTypeFromUrl(file.uri); - file.size = (await fs.promises.stat(result.path)).size; // DB file.sizeは正確とは限らないので - return { - ...result, - url: file.uri, - fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original', - file, - filename: file.name, - }; - } - - const path = this.internalStorageService.resolvePath(key); - - if (isThumbnail || isWebpublic) { - const { mime, ext } = await this.fileInfoService.detectType(path); - return { - state: 'stored_internal', - fileRole: isThumbnail ? 'thumbnail' : 'webpublic', - file, - filename: file.name, - mime, ext, - path, - }; - } - - return { - state: 'stored_internal', - fileRole: 'original', - file, - filename: file.name, - // 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる - mime: this.fileInfoService.fixMime(file.type), - ext: null, - path, - }; - } } diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 239ef82dec..93c36f5365 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -48,8 +48,6 @@ export class NodeinfoServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const nodeinfo2 = async (version: number) => { - const now = Date.now(); - const notesChart = await this.notesChart.getChart('hour', 1, null); const localPosts = notesChart.local.total[0]; diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 56c936d72f..acc7f11a34 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -6,6 +6,7 @@ import { Module } from '@nestjs/common'; import { EndpointsModule } from '@/server/api/EndpointsModule.js'; import { CoreModule } from '@/core/CoreModule.js'; +import MainStreamConnection from '@/server/api/stream/Connection.js'; import { ApiCallService } from './api/ApiCallService.js'; import { FileServerService } from './FileServerService.js'; import { HealthServerService } from './HealthServerService.js'; @@ -13,7 +14,6 @@ import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ServerService } from './ServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; import { GetterService } from './api/GetterService.js'; -import { ChannelsService } from './api/stream/ChannelsService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { ApiLoggerService } from './api/ApiLoggerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -25,30 +25,31 @@ import { SignupApiService } from './api/SignupApiService.js'; import { StreamingApiServerService } from './api/StreamingApiServerService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; +import { HtmlTemplateService } from './web/HtmlTemplateService.js'; import { FeedService } from './web/FeedService.js'; import { UrlPreviewService } from './web/UrlPreviewService.js'; import { ClientLoggerService } from './web/ClientLoggerService.js'; import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js'; -import { MainChannelService } from './api/stream/channels/main.js'; -import { AdminChannelService } from './api/stream/channels/admin.js'; -import { AntennaChannelService } from './api/stream/channels/antenna.js'; -import { ChannelChannelService } from './api/stream/channels/channel.js'; -import { DriveChannelService } from './api/stream/channels/drive.js'; -import { GlobalTimelineChannelService } from './api/stream/channels/global-timeline.js'; -import { HashtagChannelService } from './api/stream/channels/hashtag.js'; -import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js'; -import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js'; -import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js'; -import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js'; -import { ServerStatsChannelService } from './api/stream/channels/server-stats.js'; -import { UserListChannelService } from './api/stream/channels/user-list.js'; -import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js'; -import { ChatUserChannelService } from './api/stream/channels/chat-user.js'; -import { ChatRoomChannelService } from './api/stream/channels/chat-room.js'; -import { ReversiChannelService } from './api/stream/channels/reversi.js'; -import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js'; -import { MahjongRoomChannelService } from './api/stream/channels/mahjong-room.js'; +import { MainChannel } from './api/stream/channels/main.js'; +import { AdminChannel } from './api/stream/channels/admin.js'; +import { AntennaChannel } from './api/stream/channels/antenna.js'; +import { ChannelChannel } from './api/stream/channels/channel.js'; +import { DriveChannel } from './api/stream/channels/drive.js'; +import { GlobalTimelineChannel } from './api/stream/channels/global-timeline.js'; +import { HashtagChannel } from './api/stream/channels/hashtag.js'; +import { HomeTimelineChannel } from './api/stream/channels/home-timeline.js'; +import { HybridTimelineChannel } from './api/stream/channels/hybrid-timeline.js'; +import { LocalTimelineChannel } from './api/stream/channels/local-timeline.js'; +import { QueueStatsChannel } from './api/stream/channels/queue-stats.js'; +import { ServerStatsChannel } from './api/stream/channels/server-stats.js'; +import { UserListChannel } from './api/stream/channels/user-list.js'; +import { RoleTimelineChannel } from './api/stream/channels/role-timeline.js'; +import { ChatUserChannel } from './api/stream/channels/chat-user.js'; +import { ChatRoomChannel } from './api/stream/channels/chat-room.js'; +import { ReversiChannel } from './api/stream/channels/reversi.js'; +import { ReversiGameChannel } from './api/stream/channels/reversi-game.js'; +import { MahjongRoomChannel } from './api/stream/channels/mahjong-room.js'; import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js'; @Module({ @@ -59,6 +60,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j providers: [ ClientServerService, ClientLoggerService, + HtmlTemplateService, FeedService, HealthServerService, UrlPreviewService, @@ -68,7 +70,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j ServerService, WellKnownServerService, GetterService, - ChannelsService, + MainStreamConnection, ApiCallService, ApiLoggerService, ApiServerService, @@ -79,25 +81,25 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j SigninService, SignupApiService, StreamingApiServerService, - MainChannelService, - AdminChannelService, - AntennaChannelService, - ChannelChannelService, - DriveChannelService, - GlobalTimelineChannelService, - HashtagChannelService, - RoleTimelineChannelService, - ChatUserChannelService, - ChatRoomChannelService, - ReversiChannelService, - ReversiGameChannelService, - MahjongRoomChannelService, - HomeTimelineChannelService, - HybridTimelineChannelService, - LocalTimelineChannelService, - QueueStatsChannelService, - ServerStatsChannelService, - UserListChannelService, + MainChannel, + AdminChannel, + AntennaChannel, + ChannelChannel, + DriveChannel, + GlobalTimelineChannel, + HashtagChannel, + RoleTimelineChannel, + ChatUserChannel, + ChatRoomChannel, + ReversiChannel, + ReversiGameChannel, + MahjongRoomChannel, + HomeTimelineChannel, + HybridTimelineChannel, + LocalTimelineChannel, + QueueStatsChannel, + ServerStatsChannel, + UserListChannel, OpenApiServerService, OAuth2ProviderService, ], diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 355d7ca08e..ef9ac81f95 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -7,7 +7,7 @@ import cluster from 'node:cluster'; import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; -import Fastify, { FastifyInstance } from 'fastify'; +import Fastify, { type FastifyInstance } from 'fastify'; import fastifyStatic from '@fastify/static'; import fastifyRawBody from 'fastify-raw-body'; import { IsNull } from 'typeorm'; @@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown { @bindThis public async launch(): Promise { const fastify = Fastify({ - trustProxy: true, + trustProxy: this.config.trustProxy, logger: false, }); this.#fastify = fastify; @@ -108,7 +108,7 @@ export class ServerService implements OnApplicationShutdown { // this will break lookup that involve copying a URL from a third-party server, like trying to lookup http://charlie.example.com/@alice@alice.com // // this is not required by standard but protect us from peers that did not validate final URL. - if (this.config.disallowExternalApRedirect) { + if (!this.meta.allowExternalApRedirect) { const maybeApLookupRegex = /application\/activity\+json|application\/ld\+json.+activitystreams/i; fastify.addHook('onSend', (request, reply, _, done) => { const location = reply.getHeader('location'); @@ -133,8 +133,8 @@ export class ServerService implements OnApplicationShutdown { reply.header('content-type', 'text/plain; charset=utf-8'); reply.header('link', `<${encodeURI(location)}>; rel="canonical"`); done(null, [ - "Refusing to relay remote ActivityPub object lookup.", - "", + 'Refusing to relay remote ActivityPub object lookup.', + '', `Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`, ].join('\n')); }); @@ -238,30 +238,6 @@ export class ServerService implements OnApplicationShutdown { } }); - fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { - const profile = await this.userProfilesRepository.findOneBy({ - emailVerifyCode: request.params.code, - }); - - if (profile != null) { - await this.userProfilesRepository.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); - - this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - schema: 'MeDetailed', - includeSecrets: true, - })); - - reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。'); - return; - } else { - reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください'); - return; - } - }); - fastify.register(this.clientServerService.createServer); this.streamingApiServerService.attach(fastify.server); @@ -309,6 +285,13 @@ export class ServerService implements OnApplicationShutdown { await this.#fastify.close(); } + /** + * Get the Fastify instance for testing. + */ + public get fastify(): FastifyInstance { + return this.#fastify; + } + @bindThis async onApplicationShutdown(signal: string): Promise { await this.dispose(); diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index a42fdaf730..0ccb3df631 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -7,7 +7,6 @@ import { randomUUID } from 'node:crypto'; import * as fs from 'node:fs'; import * as stream from 'node:stream/promises'; import { Inject, Injectable } from '@nestjs/common'; -import * as Sentry from '@sentry/node'; import { DI } from '@/di-symbols.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; @@ -37,6 +36,7 @@ export class ApiCallService implements OnApplicationShutdown { private logger: Logger; private userIpHistories: Map>; private userIpHistoriesClearIntervalId: NodeJS.Timeout; + private Sentry: typeof import('@sentry/node') | null = null; constructor( @Inject(DI.meta) @@ -59,6 +59,12 @@ export class ApiCallService implements OnApplicationShutdown { this.userIpHistoriesClearIntervalId = setInterval(() => { this.userIpHistories.clear(); }, 1000 * 60 * 60); + + if (this.config.sentryForBackend) { + import('@sentry/node').then((Sentry) => { + this.Sentry = Sentry; + }); + } } #sendApiError(reply: FastifyReply, err: ApiError): void { @@ -120,8 +126,8 @@ export class ApiCallService implements OnApplicationShutdown { }, }); - if (this.config.sentryForBackend) { - Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, { + if (this.Sentry != null) { + this.Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, { level: 'error', user: { id: userId, @@ -307,11 +313,14 @@ export class ApiCallService implements OnApplicationShutdown { } if (ep.meta.limit) { - // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. - let limitActor: string; + let limitActor: string | null = null; if (user) { limitActor = user.id; - } else { + } else if (this.config.enableIpRateLimit) { + if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) { + this.logger.warn('Recieved API request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.'); + } + limitActor = getIpHash(request.ip); } @@ -324,21 +333,17 @@ export class ApiCallService implements OnApplicationShutdown { // TODO: 毎リクエスト計算するのもあれだしキャッシュしたい const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; - if (factor > 0) { + if (limitActor != null && factor > 0) { // Rate limit - await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor, factor).catch(err => { - if ('info' in err) { - // errはLimiter.LimiterInfoであることが期待される - throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', - httpStatusCode: 429, - }, err.info); - } else { - throw new TypeError('information must be a rate-limiter information.'); - } - }); + const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor, factor); + if (rateLimit != null) { + throw new ApiError({ + message: 'Rate limit exceeded. Please try again later.', + code: 'RATE_LIMIT_EXCEEDED', + id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + httpStatusCode: 429, + }, rateLimit.info); + } } } @@ -421,7 +426,7 @@ export class ApiCallService implements OnApplicationShutdown { if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { try { data[k] = JSON.parse(data[k]); - } catch (e) { + } catch (_) { throw new ApiError({ message: 'Invalid param.', code: 'INVALID_PARAM', @@ -436,8 +441,8 @@ export class ApiCallService implements OnApplicationShutdown { } // API invoking - if (this.config.sentryForBackend) { - return await Sentry.startSpan({ + if (this.Sentry != null) { + return await this.Sentry.startSpan({ name: 'API: ' + ep.name, }, () => ep.exec(data, user, token, file, request.ip, request.headers) .catch((err: Error) => this.#onExecError(ep, data, err, user?.id))); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 32818003ad..57d74ef2b1 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -176,6 +176,17 @@ export class ApiServerService { } }); + fastify.all('/clear-browser-cache', (request, reply) => { + if (['GET', 'POST'].includes(request.method)) { + reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"'); + reply.code(204); + reply.send(); + } else { + reply.code(405); + reply.send(); + } + }); + // Make sure any unknown path under /api returns HTTP 404 Not Found, // because otherwise ClientServerService will return the base client HTML // page with HTTP 200. diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index 444e6db744..8f4213dfb6 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -40,8 +40,8 @@ export class GetterService { } @bindThis - public async getNoteWithUser(noteId: MiNote['id']) { - const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] }); + public async getNoteWithRelations(noteId: MiNote['id']) { + const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] }); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 52d73baa0a..a730d8c60e 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -12,6 +12,14 @@ import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import type { IEndpointMeta } from './endpoints.js'; +type RateLimitInfo = { + code: 'BRIEF_REQUEST_INTERVAL', + info: Limiter.LimiterInfo, +} | { + code: 'RATE_LIMIT_EXCEEDED', + info: Limiter.LimiterInfo, +}; + @Injectable() export class RateLimiterService { private logger: Logger; @@ -31,77 +39,55 @@ export class RateLimiterService { } @bindThis - public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string, factor = 1) { - { - if (this.disabled) { - return Promise.resolve(); - } + private checkLimiter(options: Limiter.LimiterOption): Promise { + return new Promise((resolve, reject) => { + new Limiter(options).get((err, info) => { + if (err) { + return reject(err); + } + resolve(info); + }); + }); + } - // Short-term limit - const min = new Promise((ok, reject) => { - const minIntervalLimiter = new Limiter({ - id: `${actor}:${limitation.key}:min`, - duration: limitation.minInterval! * factor, - max: 1, - db: this.redisClient, - }); + @bindThis + public async limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string, factor = 1): Promise { + if (this.disabled) { + return null; + } - minIntervalLimiter.get((err, info) => { - if (err) { - return reject({ code: 'ERR', info }); - } - - this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); - - if (info.remaining === 0) { - return reject({ code: 'BRIEF_REQUEST_INTERVAL', info }); - } else { - if (hasLongTermLimit) { - return max.then(ok, reject); - } else { - return ok(); - } - } - }); + // Short-term limit + if (limitation.minInterval != null) { + const info = await this.checkLimiter({ + id: `${actor}:${limitation.key}:min`, + duration: limitation.minInterval * factor, + max: 1, + db: this.redisClient, }); - // Long term limit - const max = new Promise((ok, reject) => { - const limiter = new Limiter({ - id: `${actor}:${limitation.key}`, - duration: limitation.duration! * factor, - max: limitation.max! / factor, - db: this.redisClient, - }); + this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); - limiter.get((err, info) => { - if (err) { - return reject({ code: 'ERR', info }); - } - - this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); - - if (info.remaining === 0) { - return reject({ code: 'RATE_LIMIT_EXCEEDED', info }); - } else { - return ok(); - } - }); - }); - - const hasShortTermLimit = typeof limitation.minInterval === 'number'; - - const hasLongTermLimit = - typeof limitation.duration === 'number' && - typeof limitation.max === 'number'; - - if (hasShortTermLimit) { - return min; - } else if (hasLongTermLimit) { - return max; - } else { - return Promise.resolve(); + if (info.remaining === 0) { + return { code: 'BRIEF_REQUEST_INTERVAL', info }; } } + + // Long term limit + if (limitation.duration != null && limitation.max != null) { + const info = await this.checkLimiter({ + id: `${actor}:${limitation.key}`, + duration: limitation.duration, + max: limitation.max / factor, + db: this.redisClient, + }); + + this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); + + if (info.remaining === 0) { + return { code: 'RATE_LIMIT_EXCEEDED', info }; + } + } + + return null; } } diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 1d983ca4bc..5c9d16a95a 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -15,6 +15,7 @@ import type { UserSecurityKeysRepository, UsersRepository, } from '@/models/_.js'; +import type Logger from '@/logger.js'; import type { Config } from '@/config.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { MiLocalUser } from '@/models/User.js'; @@ -23,6 +24,7 @@ import { bindThis } from '@/decorators.js'; import { WebAuthnService } from '@/core/WebAuthnService.js'; import { UserAuthService } from '@/core/UserAuthService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; @@ -31,6 +33,8 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; @Injectable() export class SigninApiService { + private logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -50,6 +54,7 @@ export class SigninApiService { @Inject(DI.signinsRepository) private signinsRepository: SigninsRepository, + private loggerService: LoggerService, private idService: IdService, private rateLimiterService: RateLimiterService, private signinService: SigninService, @@ -57,6 +62,7 @@ export class SigninApiService { private webAuthnService: WebAuthnService, private captchaService: CaptchaService, ) { + this.logger = this.loggerService.getLogger('Signin'); } @bindThis @@ -89,18 +95,22 @@ export class SigninApiService { return { error }; } - try { // not more than 1 attempt per second and not more than 10 attempts per hour - await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip)); - } catch (err) { - reply.code(429); - return { - error: { - message: 'Too many failed attempts to sign in. Try again later.', - code: 'TOO_MANY_AUTHENTICATION_FAILURES', - id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', - }, - }; + if (this.config.enableIpRateLimit) { + if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) { + this.logger.warn('Recieved signin request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.'); + } + const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip)); + if (rateLimit != null) { + reply.code(429); + return { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + } } if (typeof username !== 'string') { @@ -221,7 +231,7 @@ export class SigninApiService { try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { return await fail(403, { id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', }); diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index 9ba23c54e2..6feb4c3afa 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -84,19 +84,25 @@ export class SigninWithPasskeyApiService { return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); }; - try { + if (this.config.enableIpRateLimit) { + if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) { + this.logger.warn('Recieved signin with passkey request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.'); + } + + try { // Not more than 1 API call per 250ms and not more than 100 attempts per 30min // NOTE: 1 Sign-in require 2 API calls - await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip)); - } catch (err) { - reply.code(429); - return { - error: { - message: 'Too many failed attempts to sign in. Try again later.', - code: 'TOO_MANY_AUTHENTICATION_FAILURES', - id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', - }, - }; + await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip)); + } catch (_) { + reply.code(429); + return { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + } } // Initiate Passkey Auth challenge with context diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 3ec5e5d3e6..b419c51ef1 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -129,7 +129,8 @@ export class SignupApiService { let ticket: MiRegistrationTicket | null = null; - if (this.meta.disableRegistration) { + // テスト時はこの機構は障害となるため無効にする + if (process.env.NODE_ENV !== 'test' && this.meta.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { reply.code(400); return; @@ -254,7 +255,7 @@ export class SignupApiService { throw new FastifyReplyError(400, 'EXPIRED'); } - const { account, secret } = await this.signupService.signup({ + const { account } = await this.signupService.signup({ username: pendingUser.username, passwordHash: pendingUser.password, }); diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 2a4e1fc574..8a317bdc4e 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -8,17 +8,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import * as WebSocket from 'ws'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, MiAccessToken } from '@/models/_.js'; -import { NotificationService } from '@/core/NotificationService.js'; +import type { MiAccessToken } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; -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 { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; -import MainStreamConnection from './stream/Connection.js'; -import { ChannelsService } from './stream/ChannelsService.js'; +import MainStreamConnection, { ConnectionRequest } from './stream/Connection.js'; import type * as http from 'node:http'; +import { ContextIdFactory, ModuleRef } from '@nestjs/core'; @Injectable() export class StreamingApiServerService { @@ -30,15 +27,9 @@ export class StreamingApiServerService { @Inject(DI.redisForSub) private redisForSub: Redis.Redis, - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private cacheService: CacheService, + private moduleRef: ModuleRef, private authenticateService: AuthenticateService, - private channelsService: ChannelsService, - private notificationService: NotificationService, private usersService: UserService, - private channelFollowingService: ChannelFollowingService, ) { } @@ -92,13 +83,12 @@ export class StreamingApiServerService { return; } - const stream = new MainStreamConnection( - this.channelsService, - this.notificationService, - this.cacheService, - this.channelFollowingService, - user, app, - ); + const contextId = ContextIdFactory.create(); + this.moduleRef.registerRequestByContextId({ + user, + token: app, + }, contextId); + const stream = await this.moduleRef.create(MainStreamConnection, contextId); await stream.init(); @@ -121,7 +111,7 @@ export class StreamingApiServerService { user: MiLocalUser | null; app: MiAccessToken | null }) => { - const { stream, user, app } = ctx; + const { stream, user } = ctx; const ev = new EventEmitter(); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index fb603c6b39..ce8aa5a7fc 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js'; export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js'; export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js'; +export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js'; export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js'; export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js'; export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js'; @@ -142,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'; @@ -168,6 +172,7 @@ export * as 'clips/update' from './endpoints/clips/update.js'; export * as 'drive' from './endpoints/drive.js'; export * as 'drive/files' from './endpoints/drive/files.js'; export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js'; +export * as 'drive/files/attached-chat-messages' from './endpoints/drive/files/attached-chat-messages.js'; export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js'; export * as 'drive/files/create' from './endpoints/drive/files/create.js'; export * as 'drive/files/delete' from './endpoints/drive/files/delete.js'; @@ -175,6 +180,7 @@ export * as 'drive/files/find' from './endpoints/drive/files/find.js'; export * as 'drive/files/find-by-hash' from './endpoints/drive/files/find-by-hash.js'; export * as 'drive/files/show' from './endpoints/drive/files/show.js'; export * as 'drive/files/update' from './endpoints/drive/files/update.js'; +export * as 'drive/files/move-bulk' from './endpoints/drive/files/move-bulk.js'; export * as 'drive/files/upload-from-url' from './endpoints/drive/files/upload-from-url.js'; export * as 'drive/folders' from './endpoints/drive/folders.js'; export * as 'drive/folders/create' from './endpoints/drive/folders/create.js'; @@ -207,6 +213,7 @@ export * as 'flash/my-likes' from './endpoints/flash/my-likes.js'; export * as 'flash/show' from './endpoints/flash/show.js'; export * as 'flash/unlike' from './endpoints/flash/unlike.js'; export * as 'flash/update' from './endpoints/flash/update.js'; +export * as 'flash/search' from './endpoints/flash/search.js'; export * as 'following/create' from './endpoints/following/create.js'; export * as 'following/delete' from './endpoints/following/delete.js'; export * as 'following/invalidate' from './endpoints/following/invalidate.js'; @@ -312,6 +319,11 @@ export * as 'notes/clips' from './endpoints/notes/clips.js'; export * as 'notes/conversation' from './endpoints/notes/conversation.js'; export * as 'notes/create' from './endpoints/notes/create.js'; export * as 'notes/delete' from './endpoints/notes/delete.js'; +export * as 'notes/drafts/list' from './endpoints/notes/drafts/list.js'; +export * as 'notes/drafts/create' from './endpoints/notes/drafts/create.js'; +export * as 'notes/drafts/delete' from './endpoints/notes/drafts/delete.js'; +export * as 'notes/drafts/update' from './endpoints/notes/drafts/update.js'; +export * as 'notes/drafts/count' from './endpoints/notes/drafts/count.js'; export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js'; export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js'; export * as 'notes/featured' from './endpoints/notes/featured.js'; @@ -329,6 +341,7 @@ export * as 'notes/replies' from './endpoints/notes/replies.js'; export * as 'notes/search' from './endpoints/notes/search.js'; export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js'; export * as 'notes/show' from './endpoints/notes/show.js'; +export * as 'notes/show-partial-bulk' from './endpoints/notes/show-partial-bulk.js'; export * as 'notes/state' from './endpoints/notes/state.js'; export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js'; export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js'; @@ -384,6 +397,7 @@ export * as 'users/featured-notes' from './endpoints/users/featured-notes.js'; export * as 'users/flashs' from './endpoints/users/flashs.js'; export * as 'users/followers' from './endpoints/users/followers.js'; export * as 'users/following' from './endpoints/users/following.js'; +export * as 'users/get-following-birthday-users' from './endpoints/users/get-following-birthday-users.js'; export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js'; export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js'; export * as 'users/lists/create' from './endpoints/users/lists/create.js'; @@ -408,6 +422,7 @@ export * as 'users/search' from './endpoints/users/search.js'; export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; export * as 'users/show' from './endpoints/users/show.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js'; +export * as 'verify-email' from './endpoints/verify-email.js'; export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js'; export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js'; export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js'; @@ -432,4 +447,5 @@ export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitat export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js'; export * as 'chat/rooms/invitations/outbox' from './endpoints/chat/rooms/invitations/outbox.js'; export * as 'chat/history' from './endpoints/chat/history.js'; +export * as 'chat/read-all' from './endpoints/chat/read-all.js'; export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 0dbfaae054..ff7133e73a 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -98,6 +98,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, state: { type: 'string', nullable: true, default: null }, reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, @@ -115,7 +117,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); switch (ps.state) { case 'resolved': query.andWhere('report.resolved = TRUE'); break; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 06047b58a6..6606202118 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -34,13 +34,22 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'MeDetailed', - properties: { - token: { - type: 'string', - optional: false, nullable: false, + allOf: [ + { + type: 'object', + ref: 'MeDetailed', }, - }, + { + type: 'object', + optional: false, nullable: false, + properties: { + token: { + type: 'string', + optional: false, nullable: false, + }, + }, + } + ], }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 955154f4fb..01697ae185 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -36,6 +36,7 @@ export const paramDef = { startsAt: { type: 'integer' }, imageUrl: { type: 'string', minLength: 1 }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], } as const; @@ -55,6 +56,7 @@ export default class extends Endpoint { // eslint- expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, @@ -73,6 +75,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, priority: ad.priority, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 6406709cda..f67cad5bd2 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, publishing: { type: 'boolean', default: null, nullable: true }, }, required: [], @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); if (ps.publishing === true) { query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() }); } else if (ps.publishing === false) { @@ -61,6 +63,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, memo: ad.memo, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 4e3d731aca..a3d9aaddc6 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -39,6 +39,7 @@ export const paramDef = { expiresAt: { type: 'integer' }, startsAt: { type: 'integer' }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['id'], } as const; @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined, startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined, dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, }); const updatedAd = await this.adsRepository.findOneByOrFail({ id: ad.id }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index b8bfda73a4..74462b302a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint- private announcementService: AnnouncementService, ) { super(meta, paramDef, async (ps, me) => { - const { raw, packed } = await this.announcementService.create({ + const { packed } = await this.announcementService.create({ updatedAt: null, title: ps.title, text: ps.text, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 7596bf44e3..aeebceed5a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -49,6 +49,36 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + icon: { + type: 'string', + optional: false, nullable: false, + enum: ['info', 'warning', 'error', 'success'], + }, + display: { + type: 'string', + optional: false, nullable: false, + enum: ['normal', 'banner', 'dialog'], + }, + isActive: { + type: 'boolean', + optional: false, nullable: false, + }, + forExistingUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + silence: { + type: 'boolean', + optional: false, nullable: false, + }, + needConfirmationToRead: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, imageUrl: { type: 'string', optional: false, nullable: true, @@ -68,6 +98,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, userId: { type: 'string', format: 'misskey:id', nullable: true }, status: { type: 'string', enum: ['all', 'active', 'archived'], default: 'active' }, }, @@ -87,7 +119,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); if (ps.status === 'archived') { query.andWhere('announcement.isActive = false'); diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts index d4d9a7235b..765bfd6766 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts @@ -71,6 +71,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, userId: { type: 'string', format: 'misskey:id', nullable: true }, }, required: [], diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 915d777e77..59b02482a2 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, userId: { type: 'string', format: 'misskey:id', nullable: true }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, @@ -57,7 +59,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); if (ps.userId) { query.andWhere('file.userId = :userId', { userId: ps.userId }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index a7136d8c8c..e7a70d0762 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -157,19 +157,42 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + maybeSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + maybePorn: { + type: 'boolean', + optional: false, nullable: false, + }, + requestIp: { + type: 'string', + optional: false, nullable: true, + }, + requestHeaders: { + type: 'object', + optional: false, nullable: true, + }, }, }, } as const; export const paramDef = { - type: 'object', - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - url: { type: 'string' }, - }, anyOf: [ - { required: ['fileId'] }, - { required: ['url'] }, + { + type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], + }, + { + type: 'object', + properties: { + url: { type: 'string' }, + }, + required: ['url'], + }, ], } as const; @@ -186,15 +209,11 @@ export default class extends Endpoint { // eslint- private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ - where: [{ - url: ps.url, - }, { - thumbnailUrl: ps.url, - }, { - webpublicUrl: ps.url, - }], - }); + const file = await this.driveFilesRepository.findOneBy( + 'fileId' in ps + ? { id: ps.fileId } + : [{ url: ps.url }, { thumbnailUrl: ps.url }, { webpublicUrl: ps.url }], + ); if (file == null) { throw new ApiError(meta.errors.noSuchFile); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index cf03859ce5..d4305e7d7c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { // eslint- try { // Create file driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); - } catch (e) { + } catch (_) { // TODO: need to return Drive Error throw new ApiError(); } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index b44007962d..b9448b4bc2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -24,39 +24,7 @@ export const meta = { optional: false, nullable: false, items: { type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - aliases: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - name: { - type: 'string', - optional: false, nullable: false, - }, - category: { - type: 'string', - optional: false, nullable: true, - }, - host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', - }, - url: { - type: 'string', - optional: false, nullable: false, - }, - }, + ref: 'EmojiDetailed', }, }, } as const; @@ -74,6 +42,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -89,7 +59,7 @@ export default class extends Endpoint { // eslint- private emojiEntityService: EmojiEntityService, ) { super(meta, paramDef, async (ps, me) => { - const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); if (ps.host == null) { q.andWhere('emoji.host IS NOT NULL'); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 4342e178cc..658367409c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -24,39 +24,7 @@ export const meta = { optional: false, nullable: false, items: { type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - aliases: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - name: { - type: 'string', - optional: false, nullable: false, - }, - category: { - type: 'string', - optional: false, nullable: true, - }, - host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', - }, - url: { - type: 'string', - optional: false, nullable: false, - }, - }, + ref: 'EmojiDetailed', }, }, } as const; @@ -68,6 +36,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -82,7 +52,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('emoji.host IS NULL'); let emojis: MiEmoji[]; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 6834a6d213..e20bc21f6b 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -37,29 +37,45 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - id: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, - fileId: { type: 'string', format: 'misskey:id' }, - category: { - type: 'string', - nullable: true, - description: 'Use `null` to reset the category.', + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + id: { type: 'string', format: 'misskey:id' }, + }, + required: ['id'], + }, + { + type: 'object', + properties: { + name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, + }, + required: ['name'], + }, + ], + }, + { + type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + category: { + type: 'string', + nullable: true, + description: 'Use `null` to reset the category.', + }, + aliases: { type: 'array', items: { + type: 'string', + } }, + license: { type: 'string', nullable: true }, + isSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: { + type: 'string', + } }, + }, }, - aliases: { type: 'array', items: { - type: 'string', - } }, - license: { type: 'string', nullable: true }, - isSensitive: { type: 'boolean' }, - localOnly: { type: 'boolean' }, - roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: { - type: 'string', - } }, - }, - anyOf: [ - { required: ['id'] }, - { required: ['name'] }, ], } as const; @@ -78,10 +94,9 @@ export default class extends Endpoint { // eslint- if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); } - // JSON schemeのanyOfの型変換がうまくいっていないらしい - const required = { id: ps.id, name: ps.name } as - | { id: MiEmoji['id']; name?: string } - | { id?: MiEmoji['id']; name: string }; + const required = 'id' in ps + ? { id: ps.id, name: 'name' in ps ? ps.name as string : undefined } + : { name: ps.name }; const error = await this.customEmojiService.update({ ...required, @@ -102,7 +117,7 @@ export default class extends Endpoint { // eslint- case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists); } // 網羅性チェック - const mustBeNever: never = error; + const _mustBeNever: never = error; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 5ecae3161a..e52b177e2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -68,6 +68,8 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insertOne({ id: this.idService.gen(), + createdBy: me, + createdById: me.id, expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), })); diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 53e2b2b237..5beed3a7e8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -223,10 +223,12 @@ export const meta = { sensitiveMediaDetection: { type: 'string', optional: false, nullable: false, + enum: ['none', 'all', 'local', 'remote'], }, sensitiveMediaDetectionSensitivity: { type: 'string', optional: false, nullable: false, + enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'], }, setSensitiveFlagAutomatically: { type: 'boolean', @@ -425,6 +427,9 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + clientOptions: { + ref: 'MetaClientOptions', + }, description: { type: 'string', optional: false, nullable: true, @@ -469,6 +474,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + feedbackUrl: { + type: 'string', + optional: false, nullable: true, + }, summalyProxy: { type: 'string', optional: false, nullable: true, @@ -495,6 +504,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + urlPreviewAllowRedirect: { + type: 'boolean', + optional: false, nullable: false, + }, urlPreviewTimeout: { type: 'number', optional: false, nullable: false, @@ -528,6 +541,61 @@ export const meta = { optional: false, nullable: false, }, }, + deliverSuspendedSoftware: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + software: { + type: 'string', + optional: false, nullable: false, + }, + versionRange: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + }, + singleUserMode: { + type: 'boolean', + optional: false, nullable: false, + }, + ugcVisibilityForVisitor: { + type: 'string', + enum: ['all', 'local', 'none'], + optional: false, nullable: false, + }, + proxyRemoteFiles: { + type: 'boolean', + optional: false, nullable: false, + }, + signToActivityPubGet: { + type: 'boolean', + optional: false, nullable: false, + }, + allowExternalApRedirect: { + type: 'boolean', + optional: false, nullable: false, + }, + enableRemoteNotesCleaning: { + type: 'boolean', + optional: false, nullable: false, + }, + remoteNotesCleaningExpiryDaysForEachNotes: { + type: 'number', + optional: false, nullable: false, + }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { + type: 'number', + optional: false, nullable: false, + }, + showRoleBadgesOfRemoteUsers: { + type: 'boolean', + optional: false, nullable: false, + }, }, }, } as const; @@ -595,6 +663,7 @@ export default class extends Endpoint { // eslint- logoImageUrl: instance.logoImageUrl, defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, + clientOptions: instance.clientOptions, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, translatorAvailable: instance.deeplAuthKey != null, @@ -665,6 +734,7 @@ export default class extends Endpoint { // eslint- notesPerOneAd: instance.notesPerOneAd, summalyProxy: instance.urlPreviewSummaryProxyUrl, urlPreviewEnabled: instance.urlPreviewEnabled, + urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect, urlPreviewTimeout: instance.urlPreviewTimeout, urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength, urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, @@ -672,6 +742,16 @@ export default class extends Endpoint { // eslint- urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, federation: instance.federation, federationHosts: instance.federationHosts, + deliverSuspendedSoftware: instance.deliverSuspendedSoftware, + singleUserMode: instance.singleUserMode, + ugcVisibilityForVisitor: instance.ugcVisibilityForVisitor, + proxyRemoteFiles: instance.proxyRemoteFiles, + signToActivityPubGet: instance.signToActivityPubGet, + allowExternalApRedirect: instance.allowExternalApRedirect, + enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning, + remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes, + remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes, + showRoleBadgesOfRemoteUsers: instance.showRoleBadgesOfRemoteUsers, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index f3e440b4cb..86158d7e22 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -52,18 +52,14 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const jobs = await this.deliverQueue.getJobs(['delayed']); - const res = [] as [string, number][]; + const counts = new Map(); for (const job of jobs) { const host = new URL(job.data.to).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } + counts.set(host, (counts.get(host) ?? 0) + 1); } - res.sort((a, b) => b[1] - a[1]); + const res = [...counts.entries()].sort((a, b) => b[1] - a[1]); return res; }); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index e7589cba81..ad6a823b8f 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -52,18 +52,14 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const jobs = await this.inboxQueue.getJobs(['delayed']); - const res = [] as [string, number][]; + const counts = new Map(); for (const job of jobs) { const host = new URL(job.data.signature.keyId).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } + counts.set(host, (counts.get(host) ?? 0) + 1); } - res.sort((a, b) => b[1] - a[1]); + const res = [...counts.entries()].sort((a, b) => b[1] - a[1]); return res; }); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts index 79731c9786..a68e95bf3f 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts @@ -5,7 +5,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; export const meta = { @@ -14,13 +13,22 @@ export const meta = { requireCredential: true, requireModerator: true, kind: 'read:admin:queue', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + optional: false, nullable: false, + ref: 'QueueJob', + }, + }, } as const; export const paramDef = { type: 'object', properties: { queue: { type: 'string', enum: QUEUE_TYPES }, - state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed'] } }, + state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed', 'paused'] } }, search: { type: 'string' }, }, required: ['queue', 'state'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts index 10ce48332a..0098160165 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts @@ -5,7 +5,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; export const meta = { @@ -14,6 +13,118 @@ export const meta = { requireCredential: true, requireModerator: true, kind: 'read:admin:queue', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + name: { + type: 'string', + optional: false, nullable: false, + enum: QUEUE_TYPES, + }, + qualifiedName: { + type: 'string', + optional: false, nullable: false, + }, + counts: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + type: 'number', + }, + }, + isPaused: { + type: 'boolean', + optional: false, nullable: false, + }, + metrics: { + type: 'object', + optional: false, nullable: false, + properties: { + completed: { + optional: false, nullable: false, + ref: 'QueueMetrics', + }, + failed: { + optional: false, nullable: false, + ref: 'QueueMetrics', + }, + }, + }, + db: { + type: 'object', + optional: false, nullable: false, + properties: { + version: { + type: 'string', + optional: false, nullable: false, + }, + mode: { + type: 'string', + optional: false, nullable: false, + enum: ['cluster', 'standalone', 'sentinel'], + }, + runId: { + type: 'string', + optional: false, nullable: false, + }, + processId: { + type: 'string', + optional: false, nullable: false, + }, + port: { + type: 'number', + optional: false, nullable: false, + }, + os: { + type: 'string', + optional: false, nullable: false, + }, + uptime: { + type: 'number', + optional: false, nullable: false, + }, + memory: { + type: 'object', + optional: false, nullable: false, + properties: { + total: { + type: 'number', + optional: false, nullable: false, + }, + used: { + type: 'number', + optional: false, nullable: false, + }, + fragmentationRatio: { + type: 'number', + optional: false, nullable: false, + }, + peak: { + type: 'number', + optional: false, nullable: false, + }, + }, + }, + clients: { + type: 'object', + optional: false, nullable: false, + properties: { + blocked: { + type: 'number', + optional: false, nullable: false, + }, + connected: { + type: 'number', + optional: false, nullable: false, + }, + }, + }, + }, + } + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts index 3a38275f60..8d27e38c84 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts @@ -5,7 +5,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; export const meta = { @@ -14,6 +13,47 @@ export const meta = { requireCredential: true, requireModerator: true, kind: 'read:admin:queue', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + name: { + type: 'string', + optional: false, nullable: false, + enum: QUEUE_TYPES, + }, + counts: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + type: 'number', + }, + }, + isPaused: { + type: 'boolean', + optional: false, nullable: false, + }, + metrics: { + type: 'object', + optional: false, nullable: false, + properties: { + completed: { + optional: false, nullable: false, + ref: 'QueueMetrics', + }, + failed: { + optional: false, nullable: false, + ref: 'QueueMetrics', + }, + }, + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts new file mode 100644 index 0000000000..b9292ed12a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts @@ -0,0 +1,45 @@ +/* + * 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 { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'read:admin:queue', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + optional: false, nullable: false, + type: 'string', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + queue: { type: 'string', enum: QUEUE_TYPES }, + jobId: { type: 'string' }, + }, + required: ['queue', 'jobId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + return this.queueService.queueGetJobLogs(ps.queue, ps.jobId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts index 63747b5540..1735c22674 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts @@ -5,7 +5,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; export const meta = { @@ -14,6 +13,11 @@ export const meta = { requireCredential: true, requireModerator: true, kind: 'read:admin:queue', + + res: { + optional: false, nullable: false, + ref: 'QueueJob', + }, } as const; export const paramDef = { @@ -28,7 +32,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - private moderationLogService: ModerationLogService, private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index d7f9e4eaa3..b69699c338 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, PostScheduledNoteQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], @@ -49,6 +49,7 @@ export default class extends Endpoint { // eslint- constructor( @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:postScheduledNote') public postScheduledNoteQueue: PostScheduledNoteQueue, @Inject('queue:deliver') public deliverQueue: DeliverQueue, @Inject('queue:inbox') public inboxQueue: InboxQueue, @Inject('queue:db') public dbQueue: DbQueue, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 198166bec2..63a8d513fd 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -49,6 +49,8 @@ export const paramDef = { roleId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['roleId'], @@ -76,7 +78,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRole); } - const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('assign.roleId = :roleId', { roleId: role.id }) .andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 80b6a4d32e..603be514c8 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -4,7 +4,6 @@ */ import * as os from 'node:os'; -import si from 'systeminformation'; import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import * as Redis from 'ioredis'; @@ -112,6 +111,8 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async () => { + const si = await import('systeminformation'); + const memStats = await si.mem(); const fsStats = await si.fsSize(); const netInterface = await si.networkInterfaceDefault(); diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 58c5f1f60a..697fdf4210 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -9,6 +9,7 @@ import type { ModerationLogsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['admin'], @@ -63,8 +64,11 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, type: { type: 'string', nullable: true }, userId: { type: 'string', format: 'misskey:id', nullable: true }, + search: { type: 'string', nullable: true }, }, required: [], } as const; @@ -79,19 +83,24 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('log'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); if (ps.type != null) { - query.andWhere('report.type = :type', { type: ps.type }); + query.andWhere('log.type = :type', { type: ps.type }); } if (ps.userId != null) { - query.andWhere('report.userId = :userId', { userId: ps.userId }); + query.andWhere('log.userId = :userId', { userId: ps.userId }); } - const reports = await query.limit(ps.limit).getMany(); + if (ps.search != null) { + const escapedSearch = sqlLikeEscape(ps.search); + query.andWhere('log.info::text ILIKE :search', { search: `%${escapedSearch}%` }); + } - return await this.moderationLogEntityService.packMany(reports); + const logs = await query.limit(ps.limit).getMany(); + + return await this.moderationLogEntityService.packMany(logs); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 1ba6853dbe..2fd7ab8ca2 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -103,6 +103,8 @@ export const meta = { quote: { optional: true, ...notificationRecieveConfig }, reaction: { optional: true, ...notificationRecieveConfig }, pollEnded: { optional: true, ...notificationRecieveConfig }, + scheduledNotePosted: { optional: true, ...notificationRecieveConfig }, + scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig }, receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts index 28071e7a33..93d293ed41 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts @@ -48,8 +48,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -57,7 +57,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts index 8d68bb8f87..e021806398 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts @@ -52,8 +52,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -62,7 +62,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index bc05587668..7a8dfc4555 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; import type { MiMeta } from '@/models/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -67,6 +68,14 @@ export const paramDef = { description: { type: 'string', nullable: true }, defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, + clientOptions: { + type: 'object', nullable: false, + properties: { + entrancePageStyle: { type: 'string', nullable: false, enum: ['classic', 'simple'] }, + showTimelineForVisitor: { type: 'boolean', nullable: false }, + showActivitiesForVisitor: { type: 'boolean', nullable: false }, + }, + }, cacheRemoteFiles: { type: 'boolean' }, cacheRemoteSensitiveFiles: { type: 'boolean' }, emailRequiredForSignup: { type: 'boolean' }, @@ -170,6 +179,7 @@ export const paramDef = { description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', }, urlPreviewEnabled: { type: 'boolean' }, + urlPreviewAllowRedirect: { type: 'boolean' }, urlPreviewTimeout: { type: 'integer' }, urlPreviewMaximumContentLength: { type: 'integer' }, urlPreviewRequireContentLength: { type: 'boolean' }, @@ -185,6 +195,29 @@ export const paramDef = { type: 'string', }, }, + deliverSuspendedSoftware: { + type: 'array', + items: { + type: 'object', + properties: { + software: { type: 'string' }, + versionRange: { type: 'string' }, + }, + required: ['software', 'versionRange'], + }, + }, + singleUserMode: { type: 'boolean' }, + ugcVisibilityForVisitor: { + type: 'string', + enum: ['all', 'local', 'none'], + }, + proxyRemoteFiles: { type: 'boolean' }, + signToActivityPubGet: { type: 'boolean' }, + allowExternalApRedirect: { type: 'boolean' }, + enableRemoteNotesCleaning: { type: 'boolean' }, + remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' }, + showRoleBadgesOfRemoteUsers: { type: 'boolean' }, }, required: [], } as const; @@ -192,6 +225,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private metaService: MetaService, private moderationLogService: ModerationLogService, ) { @@ -303,6 +339,13 @@ export default class extends Endpoint { // eslint- set.defaultDarkTheme = ps.defaultDarkTheme; } + if (ps.clientOptions !== undefined) { + set.clientOptions = { + ...serverSettings.clientOptions, + ...ps.clientOptions, + }; + } + if (ps.cacheRemoteFiles !== undefined) { set.cacheRemoteFiles = ps.cacheRemoteFiles; } @@ -645,6 +688,10 @@ export default class extends Endpoint { // eslint- set.urlPreviewEnabled = ps.urlPreviewEnabled; } + if (ps.urlPreviewAllowRedirect !== undefined) { + set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect; + } + if (ps.urlPreviewTimeout !== undefined) { set.urlPreviewTimeout = ps.urlPreviewTimeout; } @@ -671,10 +718,50 @@ export default class extends Endpoint { // eslint- set.federation = ps.federation; } + if (ps.deliverSuspendedSoftware !== undefined) { + set.deliverSuspendedSoftware = ps.deliverSuspendedSoftware; + } + if (Array.isArray(ps.federationHosts)) { set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); } + if (ps.singleUserMode !== undefined) { + set.singleUserMode = ps.singleUserMode; + } + + if (ps.ugcVisibilityForVisitor !== undefined) { + set.ugcVisibilityForVisitor = ps.ugcVisibilityForVisitor; + } + + if (ps.proxyRemoteFiles !== undefined) { + set.proxyRemoteFiles = ps.proxyRemoteFiles; + } + + if (ps.signToActivityPubGet !== undefined) { + set.signToActivityPubGet = ps.signToActivityPubGet; + } + + if (ps.allowExternalApRedirect !== undefined) { + set.allowExternalApRedirect = ps.allowExternalApRedirect; + } + + if (ps.enableRemoteNotesCleaning !== undefined) { + set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning; + } + + if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) { + set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes; + } + + if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) { + set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes; + } + + if (ps.showRoleBadgesOfRemoteUsers !== undefined) { + set.showRoleBadgesOfRemoteUsers = ps.showRoleBadgesOfRemoteUsers; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index ff8dd73605..2ad1702f72 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -33,6 +33,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, isActive: { type: 'boolean', default: true }, }, required: [], @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private announcementEntityService: AnnouncementEntityService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('announcement.isActive = :isActive', { isActive: ps.isActive }) .andWhere(new Brackets(qb => { if (me) qb.orWhere('announcement.userId = :meId', { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index a44eb6720b..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,12 +111,26 @@ 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 this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); const notes = await query.getMany(); if (sinceId != null && untilId == null) { diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 14286bc23e..ff03fce72b 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -43,7 +43,7 @@ export default class extends Endpoint { // eslint- private apResolverService: ApResolverService, ) { super(meta, paramDef, async (ps, me) => { - const resolver = this.apResolverService.createResolver(); + const resolver = await this.apResolverService.createResolver(); const object = await resolver.resolve(ps.uri); return object; }); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 4afed7dc5c..47da6b4fbd 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,9 +18,9 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], @@ -148,7 +148,7 @@ export default class extends Endpoint { // eslint- if (this.utilityService.isSelfHost(host)) return null; // リモートから一旦オブジェクトフェッチ - const resolver = this.apResolverService.createResolver(); + const resolver = await this.apResolverService.createResolver(); // allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob) const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => { if (err instanceof IdentifiableError) { @@ -215,7 +215,7 @@ export default class extends Endpoint { // eslint- type: 'Note', object, }; - } catch (e) { + } catch (_) { return null; } } diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 8431fa6b34..9885d32eda 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('blocking.blockerId = :meId', { meId: me.id }); const blockings = await query diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index e3a6d2d670..8d49b6fd0f 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -46,7 +46,7 @@ export const paramDef = { type: 'object', properties: { name: { type: 'string', minLength: 1, maxLength: 128 }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, + description: { type: 'string', nullable: true, maxLength: 2048 }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, color: { type: 'string', minLength: 1, maxLength: 16 }, isSensitive: { type: 'boolean', nullable: true }, diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index d2f36f251e..faceb03631 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -33,6 +33,8 @@ export const paramDef = { properties: { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, }, required: [], @@ -48,7 +50,15 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.channelFollowingsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + const query = this.queryService + .makePaginationQuery( + this.channelFollowingsRepository.createQueryBuilder(), + ps.sinceId, + ps.untilId, + ps.sinceDate, + ps.untilDate, + 'followeeId', + ) .andWhere({ followerId: me.id }); const followings = await query 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/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index daab685f1b..d22ac18b4b 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -33,6 +33,8 @@ export const paramDef = { properties: { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, }, required: [], @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('channel.isArchived = FALSE') .andWhere({ userId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts index ae32203603..7b6c4db91c 100644 --- a/packages/backend/src/server/api/endpoints/channels/search.ts +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -35,6 +35,8 @@ export const paramDef = { type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, }, required: ['query'], @@ -50,7 +52,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('channel.isArchived = FALSE'); if (ps.query !== '') { diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index cec5f8fd9c..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); }, @@ -121,9 +124,16 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('note.channel', 'channel'); + this.queryService.generateBaseNoteFilteringQuery(query, me); + if (me) { - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, 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 diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index dba2938b39..5ec55896e4 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -50,7 +50,7 @@ export const paramDef = { properties: { channelId: { type: 'string', format: 'misskey:id' }, name: { type: 'string', minLength: 1, maxLength: 128 }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, + description: { type: 'string', nullable: true, maxLength: 2048 }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, isArchived: { type: 'boolean', nullable: true }, pinnedNoteIds: { diff --git a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts index 63b75fb6a7..52a054303b 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchMessage: { message: 'No such message.', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/react.ts b/packages/backend/src/server/api/endpoints/chat/messages/react.ts index 5f61e7e992..2197e7bf80 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/react.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/react.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchMessage: { message: 'No such message.', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts index c0e344b889..dd598b5628 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; import { ApiError } from '@/server/api/error.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -42,6 +43,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, roomId: { type: 'string', format: 'misskey:id' }, }, required: ['roomId'], @@ -52,8 +55,12 @@ export default class extends Endpoint { // eslint- constructor( private chatEntityService: ChatEntityService, private chatService: ChatService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); const room = await this.chatService.findRoomById(ps.roomId); @@ -65,7 +72,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRoom); } - const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId); + const messages = await this.chatService.roomTimeline(room.id, ps.limit, sinceId, untilId); this.chatService.readRoomChatMessage(me.id, room.id); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts index 6784bb6ecf..adfcd232f9 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchMessage: { message: 'No such message.', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts index a057e2e088..52a13b2178 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts @@ -10,6 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; import { ApiError } from '@/server/api/error.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -43,6 +44,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, userId: { type: 'string', format: 'misskey:id' }, }, required: ['userId'], @@ -54,8 +57,12 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, private chatService: ChatService, private getterService: GetterService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); const other = await this.getterService.getUser(ps.userId).catch(err => { @@ -63,7 +70,7 @@ export default class extends Endpoint { // eslint- throw err; }); - const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId); + const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, sinceId, untilId); this.chatService.readUserChatMessage(me.id, other.id); diff --git a/packages/backend/src/server/api/endpoints/chat/read-all.ts b/packages/backend/src/server/api/endpoints/chat/read-all.ts new file mode 100644 index 0000000000..e2d9601aa6 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/chat/read-all.ts @@ -0,0 +1,40 @@ +/* + * 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 { DI } from '@/di-symbols.js'; +import { ChatService } from '@/core/ChatService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['chat'], + + requireCredential: true, + + kind: 'write:chat', + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + }, +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private chatService: ChatService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + + await this.chatService.readAllChatMessages(me.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts index 82a8e1f30d..1ea81448c1 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchRoom: { message: 'No such room.', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts index b8a228089b..88ea234527 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchRoom: { message: 'No such room.', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts index 8a02d1c704..f4f91a7d8f 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; import { ApiError } from '@/server/api/error.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -37,6 +38,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, } as const; @@ -45,11 +48,15 @@ export default class extends Endpoint { // eslint- constructor( private chatEntityService: ChatEntityService, private chatService: ChatService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); - const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId); + const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, sinceId, untilId); return this.chatEntityService.packRoomInvitations(invitations, me); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts index 0702ba086c..827bef731c 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -44,6 +45,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['roomId'], } as const; @@ -53,8 +56,12 @@ export default class extends Endpoint { // eslint- constructor( private chatService: ChatService, private chatEntityService: ChatEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); const room = await this.chatService.findMyRoomById(me.id, ps.roomId); @@ -62,7 +69,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRoom); } - const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, ps.sinceId, ps.untilId); + const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, sinceId, untilId); return this.chatEntityService.packRoomInvitations(invitations, me); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts index d561f9e03f..550b4da1a6 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchRoom: { message: 'No such room.', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts b/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts index ba9242c762..b061da6d9c 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; import { ApiError } from '@/server/api/error.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -37,6 +38,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, } as const; @@ -45,11 +48,15 @@ export default class extends Endpoint { // eslint- constructor( private chatService: ChatService, private chatEntityService: ChatEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); - const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId); + const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, sinceId, untilId); return this.chatEntityService.packRoomMemberships(memberships, me, { populateUser: false, diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts index a3ad0c2d6f..f99b408d67 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchRoom: { message: 'No such room.', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/members.ts b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts index f5ffa21d32..aa3fb5168d 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/members.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { ChatService } from '@/core/ChatService.js'; import { ApiError } from '@/server/api/error.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -43,6 +44,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['roomId'], } as const; @@ -52,8 +55,12 @@ export default class extends Endpoint { // eslint- constructor( private chatService: ChatService, private chatEntityService: ChatEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); const room = await this.chatService.findRoomById(ps.roomId); @@ -65,7 +72,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRoom); } - const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId); + const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, sinceId, untilId); return this.chatEntityService.packRoomMemberships(memberships, me, { populateUser: true, diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts index 11cbe7b8b9..ee60f92505 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts @@ -16,9 +16,6 @@ export const meta = { kind: 'write:chat', - res: { - }, - errors: { noSuchRoom: { message: 'No such room.', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts index accf7e1bee..40c954fcca 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { ChatService } from '@/core/ChatService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; import { ApiError } from '@/server/api/error.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['chat'], @@ -37,6 +38,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, } as const; @@ -45,11 +48,15 @@ export default class extends Endpoint { // eslint- constructor( private chatEntityService: ChatEntityService, private chatService: ChatService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); + await this.chatService.checkChatAvailability(me.id, 'read'); - const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId); + const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, sinceId, untilId); return this.chatEntityService.packRooms(rooms, me); }); } diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 2e4a3ff820..af20ea9f8d 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -29,7 +30,13 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + }, required: [], } as const; @@ -39,12 +46,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, + private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const clips = await this.clipsRepository.findBy({ - userId: me.id, - }); + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('clip.userId = :userId', { userId: me.id }); + + const clips = await query.limit(ps.limit).getMany(); return await this.clipEntityService.packMany(clips, me); }); diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 7638aae442..c4260fd87c 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -4,11 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -44,6 +46,9 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + search: { type: 'string', minLength: 1, maxLength: 100, nullable: true }, }, required: ['clipId'], } as const; @@ -76,7 +81,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchClip); } - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') @@ -85,10 +90,23 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser') .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateBlockedHostQueryForNote(query); + // this.queryService.generateSuspendedUserQueryForNote(query); // To avoid problems with removing notes, ignoring suspended user for now if (me) { - this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote' }); + this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); + } + + if (ps.search != null) { + for (const word of ps.search.trim().split(' ')) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + })); + } } const notes = await query diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 10c521332d..303a4d4925 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] }, @@ -51,7 +53,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('file.userId = :userId', { userId: me.id }); if (ps.folderId) { diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts new file mode 100644 index 0000000000..b34ac4abd1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts @@ -0,0 +1,93 @@ +/* + * 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 { DriveFilesRepository, ChatMessagesRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { ChatService } from '@/core/ChatService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['drive', 'chat'], + + requireCredential: true, + + kind: 'read:drive', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessage', + }, + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: '485ce26d-f5d2-4313-9783-e689d131eafb', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.chatMessagesRepository) + private chatMessagesRepository: ChatMessagesRepository, + + private chatService: ChatService, + private chatEntityService: ChatEntityService, + private queryService: QueryService, + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const isModerator = await this.roleService.isModerator(me); + + if (!isModerator) { + await this.chatService.checkChatAvailability(me.id, 'read'); + } + + const file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: isModerator ? undefined : me.id, + }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + + const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); + query.andWhere('message.fileId = :fileId', { fileId: file.id }); + + const messages = await query.limit(ps.limit).getMany(); + + return await this.chatEntityService.packMessagesDetailed(messages, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index b86059b5e7..6bc8730a1e 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -9,8 +9,8 @@ import type { NotesRepository, DriveFilesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../../error.js'; import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['drive', 'notes'], @@ -45,6 +45,8 @@ export const paramDef = { properties: { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, fileId: { type: 'string', format: 'misskey:id' }, }, @@ -75,7 +77,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchFile); } - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); query.andWhere(':file <@ note.fileIds', { file: [file.id] }); const notes = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 74eb4dded7..7d5c0ccd4d 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -10,9 +10,9 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DriveService } from '@/core/DriveService.js'; -import { ApiError } from '../../../error.js'; import { MiMeta } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['drive'], @@ -56,6 +56,19 @@ export const meta = { code: 'NO_FREE_SPACE', id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064', }, + + maxFileSizeExceeded: { + message: 'Cannot upload the file because it exceeds the maximum file size.', + code: 'MAX_FILE_SIZE_EXCEEDED', + id: 'b9d8c348-33f0-4673-b9a9-5d4da058977a', + httpStatusCode: 413, + }, + + unallowedFileType: { + message: 'Cannot upload the file because it is an unallowed file type.', + code: 'UNALLOWED_FILE_TYPE', + id: '4becd248-7f2c-48c4-a9f0-75edc4f9a1ea', + }, }, } as const; @@ -115,6 +128,8 @@ export default class extends Endpoint { // eslint- if (err instanceof IdentifiableError) { if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace); + if (err.id === 'f9e4e5f3-4df4-40b5-b400-f236945f7073') throw new ApiError(meta.errors.maxFileSizeExceeded); + if (err.id === 'bd71c601-f9b0-4808-9137-a330647ced9b') throw new ApiError(meta.errors.unallowedFileType); } throw new ApiError(); } finally { diff --git a/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts b/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts new file mode 100644 index 0000000000..c8500895eb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts @@ -0,0 +1,41 @@ +/* + * 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 { DI } from '@/di-symbols.js'; +import { DriveService } from '@/core/DriveService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['drive'], + + requireCredential: true, + + kind: 'write:drive', + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 100, items: { type: 'string', format: 'misskey:id' } }, + folderId: { type: 'string', format: 'misskey:id', nullable: true }, + }, + required: ['fileIds'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.driveService.moveFiles(ps.fileIds, ps.folderId ?? null, me.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index e8f4539d61..9a2e2c73e8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -43,14 +43,21 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - url: { type: 'string' }, - }, anyOf: [ - { required: ['fileId'] }, - { required: ['url'] }, + { + type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], + }, + { + type: 'object', + properties: { + url: { type: 'string' }, + }, + required: ['url'], + }, ], } as const; @@ -64,21 +71,11 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - let file: MiDriveFile | null = null; - - if (ps.fileId) { - file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - } else if (ps.url) { - file = await this.driveFilesRepository.findOne({ - where: [{ - url: ps.url, - }, { - webpublicUrl: ps.url, - }, { - thumbnailUrl: ps.url, - }], - }); - } + const file = await this.driveFilesRepository.findOneBy( + 'fileId' in ps + ? { id: ps.fileId } + : [{ url: ps.url }, { webpublicUrl: ps.url }, { thumbnailUrl: ps.url }], + ); if (file == null) { throw new ApiError(meta.errors.noSuchFile); diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index 8c4848f8e1..23b3db60a8 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, }, required: [], @@ -49,7 +51,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('folder.userId = :userId', { userId: me.id }); if (ps.folderId) { diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index f7c1ed39b5..8bf83a9653 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, type: { type: 'string', pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, }, required: [], @@ -49,7 +51,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('file.userId = :userId', { userId: me.id }); if (ps.type) { diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index ce4dd13067..296bc7c5a8 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -32,6 +32,8 @@ export const paramDef = { host: { type: 'string' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['host'], @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('following.followeeHost = :host', { host: ps.host }); const followings = await query diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index 1a793889c7..091bf442af 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -32,6 +32,8 @@ export const paramDef = { host: { type: 'string' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['host'], @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('following.followerHost = :host', { host: ps.host }); const followings = await query diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 71b1aeb07b..c2d660f163 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -32,6 +32,8 @@ export const paramDef = { host: { type: 'string' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['host'], @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('user.host = :host', { host: ps.host }); const users = await query diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts index 755cc5acfc..ff9d6c3264 100644 --- a/packages/backend/src/server/api/endpoints/flash/my-likes.ts +++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts @@ -5,10 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FlashLikesRepository } from '@/models/_.js'; -import { QueryService } from '@/core/QueryService.js'; import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js'; import { DI } from '@/di-symbols.js'; +import { FlashService } from '@/core/FlashService.js'; export const meta = { tags: ['account', 'flash'], @@ -44,6 +43,9 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + search: { type: 'string', minLength: 1, maxLength: 100, nullable: true }, }, required: [], } as const; @@ -51,20 +53,18 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.flashLikesRepository) - private flashLikesRepository: FlashLikesRepository, - private flashLikeEntityService: FlashLikeEntityService, - private queryService: QueryService, + private flashService: FlashService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) - .andWhere('like.userId = :meId', { meId: me.id }) - .leftJoinAndSelect('like.flash', 'flash'); - - const likes = await query - .limit(ps.limit) - .getMany(); + const likes = await this.flashService.myLikes(me.id, { + sinceId: ps.sinceId, + untilId: ps.untilId, + sinceDate: ps.sinceDate, + untilDate: ps.untilDate, + limit: ps.limit, + search: ps.search, + }); return this.flashLikeEntityService.packMany(likes, me); }); diff --git a/packages/backend/src/server/api/endpoints/flash/my.ts b/packages/backend/src/server/api/endpoints/flash/my.ts index 5746096232..6ec623b719 100644 --- a/packages/backend/src/server/api/endpoints/flash/my.ts +++ b/packages/backend/src/server/api/endpoints/flash/my.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('flash.userId = :meId', { meId: me.id }); const flashs = await query diff --git a/packages/backend/src/server/api/endpoints/flash/search.ts b/packages/backend/src/server/api/endpoints/flash/search.ts new file mode 100644 index 0000000000..36948bb7b4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/flash/search.ts @@ -0,0 +1,59 @@ +/* + * 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 { FlashEntityService } from '@/core/entities/FlashEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { FlashService } from '@/core/FlashService.js'; + +export const meta = { + tags: ['flash'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Flash', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + query: { type: 'string', minLength: 1, maxLength: 100 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, + }, + required: ['query'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private flashService: FlashService, + private flashEntityService: FlashEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const result = await this.flashService.search(ps.query, { + sinceId: ps.sinceId, + untilId: ps.untilId, + sinceDate: ps.sinceDate, + untilDate: ps.untilDate, + limit: ps.limit, + }); + + return await this.flashEntityService.packMany(result, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index e378669f0a..8696c6f6e8 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -73,8 +73,8 @@ export default class extends Endpoint { // eslint- updatedAt: new Date(), ...Object.fromEntries( Object.entries(ps).filter( - ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key) - ) + ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key), + ), ), }); }); diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index fa59e38976..cf614e433d 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -49,6 +49,8 @@ export const paramDef = { properties: { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: [], @@ -64,7 +66,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('request.followeeId = :meId', { meId: me.id }); const requests = await query diff --git a/packages/backend/src/server/api/endpoints/following/requests/sent.ts b/packages/backend/src/server/api/endpoints/following/requests/sent.ts index 6325f01bb8..2b3cc35f03 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/sent.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/sent.ts @@ -49,6 +49,8 @@ export const paramDef = { properties: { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: [], @@ -64,7 +66,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('request.followerId = :meId', { meId: me.id }); const requests = await query diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index d398418ab4..846b903bdb 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -30,6 +30,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -44,7 +46,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .innerJoinAndSelect('post.user', 'user'); const posts = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 65eece5b97..8dc5cafb56 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -81,7 +81,7 @@ export default class extends Endpoint { try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 9391aee5e0..050dbaf49e 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -212,7 +212,7 @@ export default class extends Endpoint { try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index a54c598213..b6c837eda7 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index c350136eae..6e5d9943de 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -61,7 +61,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index b5a53cc889..23b577dc18 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -57,7 +57,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 055b5cc061..523d81ac73 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -46,6 +46,14 @@ export const meta = { type: 'string', }, }, + iconUrl: { + type: 'string', + optional: true, nullable: true, + }, + description: { + type: 'string', + optional: true, nullable: true, + }, }, }, }, @@ -88,6 +96,8 @@ export default class extends Endpoint { // eslint- createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt?.toISOString(), permission: token.app ? token.app.permission : token.permission, + iconUrl: token.iconUrl, + description: token.description ?? token.app?.description ?? null, }))); }); } diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index bb78d47149..19ea187ee8 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -45,7 +45,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts index e70905ef1b..0e42647ef7 100644 --- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts +++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts @@ -5,7 +5,8 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; +import { AchievementService } from '@/core/AchievementService.js'; +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; export const meta = { requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index bfa0b4605d..42324c7778 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -49,7 +49,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 3558035eca..00277282ba 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('favorite.userId = :meId', { meId: me.id }) .leftJoinAndSelect('favorite.note', 'note'); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index d492585ffa..9913ed7489 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -45,6 +45,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -59,7 +61,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.galleryLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.galleryLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('like.userId = :meId', { meId: me.id }) .leftJoinAndSelect('like.post', 'post'); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index 73a6fcc98b..c9d17fc35c 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('post.userId = :meId', { meId: me.id }); const posts = await query diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts index b9c41b057d..4fe39bb8e8 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts @@ -49,6 +49,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, markAsRead: { type: 'boolean', default: true }, // 後方互換のため、廃止された通知タイプも受け付ける includeTypes: { type: 'array', items: { @@ -64,15 +66,13 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, - private idService: IdService, private notificationEntityService: NotificationEntityService, private notificationService: NotificationService, ) { super(meta, paramDef, async (ps, me) => { - const EXTRA_LIMIT = 100; + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : undefined); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : undefined); // includeTypes が空の場合はクエリしない if (ps.includeTypes && ps.includeTypes.length === 0) { @@ -87,8 +87,8 @@ export default class extends Endpoint { // eslint- const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][]; const notifications = await this.notificationService.getNotifications(me.id, { - sinceId: ps.sinceId, - untilId: ps.untilId, + sinceId: sinceId, + untilId: untilId, limit: ps.limit, includeTypes, excludeTypes, diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index f5a48b2f69..158cc9fd73 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -44,6 +44,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, markAsRead: { type: 'boolean', default: true }, // 後方互換のため、廃止された通知タイプも受け付ける includeTypes: { type: 'array', items: { @@ -59,17 +61,14 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - private idService: IdService, private notificationEntityService: NotificationEntityService, private notificationService: NotificationService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : undefined); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : undefined); + // includeTypes が空の場合はクエリしない if (ps.includeTypes && ps.includeTypes.length === 0) { return []; @@ -83,8 +82,8 @@ export default class extends Endpoint { // eslint- const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; const notifications = await this.notificationService.getNotifications(me.id, { - sinceId: ps.sinceId, - untilId: ps.untilId, + sinceId: sinceId, + untilId: untilId, limit: ps.limit, includeTypes, excludeTypes, diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index d4c09426a7..d62dd819f8 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -44,6 +44,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -58,7 +60,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.pageLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.pageLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('like.userId = :meId', { meId: me.id }) .leftJoinAndSelect('like.page', 'page'); diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 1b6359a633..385842539e 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('page.userId = :meId', { meId: me.id }); const pages = await query diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index c05ee93c6f..08f5e3a7a1 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -15,14 +15,21 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - tokenId: { type: 'string', format: 'misskey:id' }, - token: { type: 'string', nullable: true }, - }, anyOf: [ - { required: ['tokenId'] }, - { required: ['token'] }, + { + type: 'object', + properties: { + tokenId: { type: 'string', format: 'misskey:id' }, + }, + required: ['tokenId'], + }, + { + type: 'object', + properties: { + token: { type: 'string', nullable: true }, + }, + required: ['token'], + }, ], } as const; @@ -33,7 +40,7 @@ export default class extends Endpoint { // eslint- private accessTokensRepository: AccessTokensRepository, ) { super(meta, paramDef, async (ps, me) => { - if (ps.tokenId) { + if ('tokenId' in ps) { const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } }); if (tokenExist) { diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 76ad0bbe21..c104dfdcdd 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -31,6 +31,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -45,7 +47,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('signin.userId = :meId', { meId: me.id }); const history = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index da1faee30d..c2f4281f36 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -91,7 +91,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (e) { + } catch (_) { throw new Error('authentication failed'); } } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 082d97f5d4..5207d9f2b0 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -7,7 +7,7 @@ import RE2 from 're2'; import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import { JSDOM } from 'jsdom'; +import * as htmlParser from 'node-html-parser'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; @@ -209,6 +209,8 @@ export const paramDef = { quote: notificationRecieveConfig, reaction: notificationRecieveConfig, pollEnded: notificationRecieveConfig, + scheduledNotePosted: notificationRecieveConfig, + scheduledNotePostFailed: notificationRecieveConfig, receiveFollowRequest: notificationRecieveConfig, followRequestAccepted: notificationRecieveConfig, roleAssigned: notificationRecieveConfig, @@ -293,8 +295,20 @@ export default class extends Endpoint { // eslint- if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope; function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) { - // TODO: ちゃんと数える - const length = JSON.stringify(mutedWords).length; + const count = (arr: (string[] | string)[]) => { + let length = 0; + for (const item of arr) { + if (typeof item === 'string') { + length += item.length; + } else if (Array.isArray(item)) { + for (const subItem of item) { + length += subItem.length; + } + } + } + return length; + }; + const length = count(mutedWords); if (length > limit) { throw new ApiError(meta.errors.tooManyMutedWords); } @@ -309,7 +323,7 @@ export default class extends Endpoint { // eslint- try { new RE2(regexp[1], regexp[2]); - } catch (err) { + } catch (_) { throw new ApiError(meta.errors.invalidRegexp); } } @@ -555,16 +569,15 @@ export default class extends Endpoint { // eslint- try { const html = await this.httpRequestService.getHtml(url); - const { window } = new JSDOM(html); - const doc: Document = window.document; + const doc = htmlParser.parse(html); const myLink = `${this.config.url}/@${user.username}`; const aEls = Array.from(doc.getElementsByTagName('a')); const linkEls = Array.from(doc.getElementsByTagName('link')); - const includesMyLink = aEls.some(a => a.href === myLink); - const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); + const includesMyLink = aEls.some(a => a.attributes.href === myLink); + const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.attributes.rel?.split(/\s+/).includes('me') && link.attributes.href === myLink); if (includesMyLink || includesRelMeLinks) { await this.userProfilesRepository.createQueryBuilder('profile').update() @@ -574,9 +587,7 @@ export default class extends Endpoint { // eslint- }) .execute(); } - - window.close(); - } catch (err) { + } catch (_) { // なにもしない } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 394c178f2a..8a3ba9e026 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -21,29 +21,7 @@ export const meta = { type: 'array', items: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, }, } as const; @@ -65,19 +43,17 @@ export default class extends Endpoint { // eslint- userId: me.id, }); - return webhooks.map(webhook => ( - { - id: webhook.id, - userId: webhook.userId, - name: webhook.name, - on: webhook.on, - url: webhook.url, - secret: webhook.secret, - active: webhook.active, - latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, - latestStatus: webhook.latestStatus, - } - )); + return webhooks.map(webhook => ({ + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, + latestStatus: webhook.latestStatus, + })); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 4a0c09ff0c..1c19081c98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -28,29 +28,7 @@ export const meta = { res: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, } as const; diff --git a/packages/backend/src/server/api/endpoints/invite/list.ts b/packages/backend/src/server/api/endpoints/invite/list.ts index a99974a91e..71f8f579a5 100644 --- a/packages/backend/src/server/api/endpoints/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/invite/list.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.registrationTicketsRepository.createQueryBuilder('ticket'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.registrationTicketsRepository.createQueryBuilder('ticket'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('ticket.createdById = :meId', { meId: me.id }) .leftJoinAndSelect('ticket.createdBy', 'createdBy') .leftJoinAndSelect('ticket.usedBy', 'usedBy'); diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 23204f2829..7762556bee 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.mutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.mutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('muting.muterId = :meId', { meId: me.id }); const mutings = await query diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 9938322a2a..0800828a87 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -35,6 +35,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -49,7 +51,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.visibility = \'public\'') .andWhere('note.localOnly = FALSE') .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index e73c98282c..e9d56e2892 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['noteId'], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere(new Brackets(qb => { qb .where('note.replyId = :noteId', { noteId: ps.noteId }) @@ -70,10 +72,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) { - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); - } + this.queryService.generateBaseNoteFilteringQuery(query, me); const notes = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 253a360815..e48aa69d0f 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -6,17 +6,10 @@ import ms from 'ms'; import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { MiUser } from '@/models/User.js'; -import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiNote } from '@/models/Note.js'; -import type { MiChannel } from '@/models/Channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; -import { DI } from '@/di-symbols.js'; -import { isQuote, isRenote } from '@/misc/is-renote.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; @@ -223,162 +216,28 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.channelsRepository) - private channelsRepository: ChannelsRepository, - private noteEntityService: NoteEntityService, private noteCreateService: NoteCreateService, ) { super(meta, paramDef, async (ps, me) => { - let visibleUsers: MiUser[] = []; - if (ps.visibleUserIds) { - visibleUsers = await this.usersRepository.findBy({ - id: In(ps.visibleUserIds), - }); - } - - let files: MiDriveFile[] = []; - const fileIds = ps.fileIds ?? ps.mediaIds ?? null; - if (fileIds != null) { - files = await this.driveFilesRepository.createQueryBuilder('file') - .where('file.userId = :userId AND file.id IN (:...fileIds)', { - userId: me.id, - fileIds, - }) - .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') - .setParameters({ fileIds }) - .getMany(); - - if (files.length !== fileIds.length) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - let renote: MiNote | null = null; - if (ps.renoteId != null) { - // Fetch renote to note - renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); - - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (isRenote(renote) && !isQuote(renote)) { - throw new ApiError(meta.errors.cannotReRenote); - } - - // Check blocking - if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: renote.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - if (renote.visibility === 'followers' && renote.userId !== me.id) { - // 他人のfollowers noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } else if (renote.visibility === 'specified') { - // specified / direct noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } - - if (renote.channelId && renote.channelId !== ps.channelId) { - // チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック - // リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する - const renoteChannel = await this.channelsRepository.findOneBy({ id: renote.channelId }); - if (renoteChannel == null) { - // リノートしたいノートが書き込まれているチャンネルが無い - throw new ApiError(meta.errors.noSuchChannel); - } else if (!renoteChannel.allowRenoteToExternal) { - // リノート作成のリクエストだが、対象チャンネルがリノート禁止だった場合 - throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel); - } - } - } - - let reply: MiNote | null = null; - if (ps.replyId != null) { - // Fetch reply - reply = await this.notesRepository.findOneBy({ id: ps.replyId }); - - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isRenote(reply) && !isQuote(reply)) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) { - throw new ApiError(meta.errors.cannotReplyToInvisibleNote); - } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') { - throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); - } - - // Check blocking - if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: reply.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - } - - if (ps.poll) { - if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { - throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); - } - } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; - } - } - - let channel: MiChannel | null = null; - if (ps.channelId != null) { - channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } - } - - // 投稿を作成 try { - const note = await this.noteCreateService.create(me, { + const note = await this.noteCreateService.fetchAndCreate(me, { createdAt: new Date(), - files: files, + fileIds: ps.fileIds ?? ps.mediaIds ?? [], poll: ps.poll ? { choices: ps.poll.choices, multiple: ps.poll.multiple ?? false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - } : undefined, - text: ps.text ?? undefined, - reply, - renote, - cw: ps.cw, + expiresAt: ps.poll.expiredAfter ? new Date(Date.now() + ps.poll.expiredAfter) : ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } : null, + text: ps.text ?? null, + replyId: ps.replyId ?? null, + renoteId: ps.renoteId ?? null, + cw: ps.cw ?? null, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUsers, - channel, + visibleUserIds: ps.visibleUserIds ?? [], + channelId: ps.channelId ?? null, apMentions: ps.noExtractMentions ? [] : undefined, apHashtags: ps.noExtractHashtags ? [] : undefined, apEmojis: ps.noExtractEmojis ? [] : undefined, @@ -387,16 +246,46 @@ export default class extends Endpoint { // eslint- return { createdNote: await this.noteEntityService.pack(note, me), }; - } catch (e) { + } catch (err) { // TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい - if (e instanceof IdentifiableError) { - if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { + if (err instanceof IdentifiableError) { + if (err.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { throw new ApiError(meta.errors.containsProhibitedWords); - } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { + } else if (err.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { throw new ApiError(meta.errors.containsTooManyMentions); + } else if (err.id === '801c046c-5bf5-4234-ad2b-e78fc20a2ac7') { + throw new ApiError(meta.errors.noSuchFile); + } else if (err.id === '53983c56-e163-45a6-942f-4ddc485d4290') { + throw new ApiError(meta.errors.noSuchRenoteTarget); + } else if (err.id === 'bde24c37-121f-4e7d-980d-cec52f599f02') { + throw new ApiError(meta.errors.cannotReRenote); + } else if (err.id === '2b4fe776-4414-4a2d-ae39-f3418b8fd4d3') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (err.id === '90b9d6f0-893a-4fef-b0f1-e9a33989f71a') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (err.id === '48d7a997-da5c-4716-b3c3-92db3f37bf7d') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (err.id === 'b060f9a6-8909-4080-9e0b-94d9fa6f6a77') { + throw new ApiError(meta.errors.noSuchChannel); + } else if (err.id === '7e435f4a-780d-4cfc-a15a-42519bd6fb67') { + throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel); + } else if (err.id === '60142edb-1519-408e-926d-4f108d27bee0') { + throw new ApiError(meta.errors.noSuchReplyTarget); + } else if (err.id === 'f089e4e2-c0e7-4f60-8a23-e5a6bf786b36') { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } else if (err.id === '11cd37b3-a411-4f77-8633-c580ce6a8dce') { + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); + } else if (err.id === 'ced780a1-2012-4caf-bc7e-a95a291294cb') { + throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); + } else if (err.id === 'b0df6025-f2e8-44b4-a26a-17ad99104612') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (err.id === '0c11c11e-0c8d-48e7-822c-76ccef660068') { + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + } else if (err.id === 'bfa3905b-25f5-4894-b430-da331a490e4b') { + throw new ApiError(meta.errors.noSuchChannel); } } - throw e; + throw err; } }); } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/count.ts b/packages/backend/src/server/api/endpoints/notes/drafts/count.ts new file mode 100644 index 0000000000..002a545d32 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/drafts/count.ts @@ -0,0 +1,51 @@ +/* + * 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 { NoteDraftsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['notes', 'drafts'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'read:account', + + res: { + type: 'number', + optional: false, nullable: false, + description: 'The number of drafts', + }, + + errors: { + }, +} 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( + @Inject(DI.noteDraftsRepository) + private noteDraftsRepository: NoteDraftsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const count = await this.noteDraftsRepository.createQueryBuilder('drafts') + .where('drafts.userId = :meId', { meId: me.id }) + .getCount(); + + return count; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts new file mode 100644 index 0000000000..efb5ee01d1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts @@ -0,0 +1,285 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDraftService } from '@/core/NoteDraftService.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { ApiError } from '@/server/api/error.js'; +import { NoteDraftEntityService } from '@/core/entities/NoteDraftEntityService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; + +export const meta = { + tags: ['notes', 'drafts'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'write:account', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + createdDraft: { + type: 'object', + optional: false, nullable: false, + ref: 'NoteDraft', + }, + }, + }, + + errors: { + noSuchRenoteTarget: { + message: 'No such renote target.', + code: 'NO_SUCH_RENOTE_TARGET', + id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4', + }, + + cannotReRenote: { + message: 'You can not Renote a pure Renote.', + code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE', + id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a', + }, + + cannotRenoteDueToVisibility: { + message: 'You can not Renote due to target visibility.', + code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY', + id: 'be9529e9-fe72-4de0-ae43-0b363c4938af', + }, + + noSuchReplyTarget: { + message: 'No such reply target.', + code: 'NO_SUCH_REPLY_TARGET', + id: '749ee0f6-d3da-459a-bf02-282e2da4292c', + }, + + cannotReplyToInvisibleNote: { + message: 'You cannot reply to an invisible Note.', + code: 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE', + id: 'b98980fa-3780-406c-a935-b6d0eeee10d1', + }, + + cannotReplyToPureRenote: { + message: 'You can not reply to a pure Renote.', + code: 'CANNOT_REPLY_TO_A_PURE_RENOTE', + id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', + }, + + cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: { + message: 'You cannot reply to a specified visibility note with extended visibility.', + code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', + id: 'ed940410-535c-4d5e-bfa3-af798671e93c', + }, + + cannotCreateAlreadyExpiredPoll: { + message: 'Poll is already expired.', + code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', + id: '04da457d-b083-4055-9082-955525eda5a5', + }, + + noSuchChannel: { + message: 'No such channel.', + code: 'NO_SUCH_CHANNEL', + id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb', + }, + + youHaveBeenBlocked: { + message: 'You have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', + }, + + noSuchFile: { + message: 'Some files are not found.', + code: 'NO_SUCH_FILE', + id: 'b6992544-63e7-67f0-fa7f-32444b1b5306', + }, + + cannotRenoteOutsideOfChannel: { + message: 'Cannot renote outside of channel.', + code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL', + id: '33510210-8452-094c-6227-4a6c05d99f00', + }, + + containsProhibitedWords: { + message: 'Cannot post because it contains prohibited words.', + code: 'CONTAINS_PROHIBITED_WORDS', + id: 'aa6e01d3-a85c-669d-758a-76aab43af334', + }, + + containsTooManyMentions: { + message: 'Cannot post because it exceeds the allowed number of mentions.', + code: 'CONTAINS_TOO_MANY_MENTIONS', + id: '4de0363a-3046-481b-9b0f-feff3e211025', + }, + + tooManyDrafts: { + message: 'You cannot create drafts any more.', + code: 'TOO_MANY_DRAFTS', + id: '9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', + }, + + tooManyScheduledNotes: { + message: 'You cannot create scheduled notes any more.', + code: 'TOO_MANY_SCHEDULED_NOTES', + id: '22ae69eb-09e3-4541-a850-773cfa45e693', + }, + + cannotRenoteToExternal: { + message: 'Cannot Renote to External.', + code: 'CANNOT_RENOTE_TO_EXTERNAL', + id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7', + }, + + scheduledAtRequired: { + message: 'scheduledAt is required when isActuallyScheduled is true.', + code: 'SCHEDULED_AT_REQUIRED', + id: '15e28a55-e74c-4d65-89b7-8880cdaaa87d', + }, + + scheduledAtMustBeInFuture: { + message: 'scheduledAt must be in the future.', + code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE', + id: 'e4bed6c9-017e-4934-aed0-01c22cc60ec1', + }, + }, + + limit: { + duration: ms('1hour'), + max: 300, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, + visibleUserIds: { type: 'array', uniqueItems: true, items: { + type: 'string', format: 'misskey:id', + } }, + cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 }, + hashtag: { type: 'string', nullable: true, maxLength: 200 }, + localOnly: { type: 'boolean', default: false }, + reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, + replyId: { type: 'string', format: 'misskey:id', nullable: true }, + renoteId: { type: 'string', format: 'misskey:id', nullable: true }, + channelId: { type: 'string', format: 'misskey:id', nullable: true }, + + // anyOf内にバリデーションを書いても最初の一つしかチェックされない + text: { + type: 'string', + minLength: 0, + maxLength: MAX_NOTE_TEXT_LENGTH, + nullable: true, + }, + fileIds: { + type: 'array', + uniqueItems: true, + minItems: 0, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, + }, + poll: { + type: 'object', + nullable: true, + properties: { + choices: { + type: 'array', + uniqueItems: true, + minItems: 0, + maxItems: 10, + items: { type: 'string', minLength: 1, maxLength: 50 }, + }, + multiple: { type: 'boolean' }, + expiresAt: { type: 'integer', nullable: true }, + expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, + }, + required: ['choices'], + }, + scheduledAt: { type: 'integer', nullable: true }, + isActuallyScheduled: { type: 'boolean', default: false }, + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private noteDraftService: NoteDraftService, + private noteDraftEntityService: NoteDraftEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const draft = await this.noteDraftService.create(me, { + fileIds: ps.fileIds ?? [], + pollChoices: ps.poll?.choices ?? [], + pollMultiple: ps.poll?.multiple ?? false, + pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null, + pollExpiredAfter: ps.poll?.expiredAfter ?? null, + hasPoll: ps.poll != null, + text: ps.text ?? null, + replyId: ps.replyId ?? null, + renoteId: ps.renoteId ?? null, + cw: ps.cw ?? null, + hashtag: ps.hashtag ?? null, + localOnly: ps.localOnly, + reactionAcceptance: ps.reactionAcceptance, + visibility: ps.visibility, + visibleUserIds: ps.visibleUserIds ?? [], + channelId: ps.channelId ?? null, + scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, + isActuallyScheduled: ps.isActuallyScheduled, + }).catch((err) => { + if (err instanceof IdentifiableError) { + switch (err.id) { + case '9ee33bbe-fde3-4c71-9b51-e50492c6b9c8': + throw new ApiError(meta.errors.tooManyDrafts); + case '04da457d-b083-4055-9082-955525eda5a5': + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + case 'b6992544-63e7-67f0-fa7f-32444b1b5306': + throw new ApiError(meta.errors.noSuchFile); + case '64929870-2540-4d11-af41-3b484d78c956': + throw new ApiError(meta.errors.noSuchRenoteTarget); + case '76cc5583-5a14-4ad3-8717-0298507e32db': + throw new ApiError(meta.errors.cannotReRenote); + case '075ca298-e6e7-485a-b570-51a128bb5168': + throw new ApiError(meta.errors.youHaveBeenBlocked); + case '81eb8188-aea1-4e35-9a8f-3334a3be9855': + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + case '6815399a-6f13-4069-b60d-ed5156249d12': + throw new ApiError(meta.errors.noSuchChannel); + case 'ed1952ac-2d26-4957-8b30-2deda76bedf7': + throw new ApiError(meta.errors.cannotRenoteToExternal); + case 'c4721841-22fc-4bb7-ad3d-897ef1d375b5': + throw new ApiError(meta.errors.noSuchReplyTarget); + case 'e6c10b57-2c09-4da3-bd4d-eda05d51d140': + throw new ApiError(meta.errors.cannotReplyToPureRenote); + case '593c323c-6b6a-4501-a25c-2f36bd2a93d6': + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); + case '215dbc76-336c-4d2a-9605-95766ba7dab0': + throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); + case 'c3275f19-4558-4c59-83e1-4f684b5fab66': + throw new ApiError(meta.errors.tooManyScheduledNotes); + case '94a89a43-3591-400a-9c17-dd166e71fdfa': + throw new ApiError(meta.errors.scheduledAtRequired); + case 'b34d0c1b-996f-4e34-a428-c636d98df457': + throw new ApiError(meta.errors.scheduledAtMustBeInFuture); + default: + throw err; + } + } + throw err; + }); + + const createdDraft = await this.noteDraftEntityService.pack(draft, me); + + return { + createdDraft, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/delete.ts b/packages/backend/src/server/api/endpoints/notes/drafts/delete.ts new file mode 100644 index 0000000000..6c41145c18 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/drafts/delete.ts @@ -0,0 +1,61 @@ +/* + * 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 { NoteDraftService } from '@/core/NoteDraftService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['notes', 'drafts'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'write:account', + + errors: { + noSuchNoteDraft: { + message: 'No such note draft.', + code: 'NO_SUCH_NOTE_DRAFT', + id: '49cd6b9d-848e-41ee-b0b9-adaca711a6b1', + }, + + accessDenied: { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + draftId: { type: 'string', nullable: false, format: 'misskey:id' }, + }, + required: ['draftId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private noteDraftService: NoteDraftService, + ) { + super(meta, paramDef, async (ps, me) => { + const draft = await this.noteDraftService.get(me, ps.draftId); + if (draft == null) { + throw new ApiError(meta.errors.noSuchNoteDraft); + } + + if (draft.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } + + await this.noteDraftService.delete(me, draft.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/list.ts b/packages/backend/src/server/api/endpoints/notes/drafts/list.ts new file mode 100644 index 0000000000..0774f09228 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/drafts/list.ts @@ -0,0 +1,75 @@ +/* + * 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 { MiNoteDraft, NoteDraftsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteDraftEntityService } from '@/core/entities/NoteDraftEntityService.js'; + +export const meta = { + tags: ['notes', 'drafts'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'read:account', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'NoteDraft', + }, + }, + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + scheduled: { type: 'boolean', nullable: true }, + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.noteDraftsRepository) + private noteDraftsRepository: NoteDraftsRepository, + + private queryService: QueryService, + private noteDraftEntityService: NoteDraftEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.noteDraftsRepository.createQueryBuilder('drafts'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('drafts.userId = :meId', { meId: me.id }); + + if (ps.scheduled === true) { + query.andWhere('drafts.isActuallyScheduled = true'); + } else if (ps.scheduled === false) { + query.andWhere('drafts.isActuallyScheduled = false'); + } + + const drafts = await query + .limit(ps.limit) + .getMany(); + + return await this.noteDraftEntityService.packMany(drafts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts new file mode 100644 index 0000000000..2900e0cb0d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts @@ -0,0 +1,328 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDraftService } from '@/core/NoteDraftService.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { NoteDraftEntityService } from '@/core/entities/NoteDraftEntityService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['notes', 'drafts'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'write:account', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + updatedDraft: { + type: 'object', + optional: false, nullable: false, + ref: 'NoteDraft', + }, + }, + }, + + errors: { + noSuchRenoteTarget: { + message: 'No such renote target.', + code: 'NO_SUCH_RENOTE_TARGET', + id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4', + }, + + cannotReRenote: { + message: 'You can not Renote a pure Renote.', + code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE', + id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a', + }, + + cannotRenoteDueToVisibility: { + message: 'You can not Renote due to target visibility.', + code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY', + id: 'be9529e9-fe72-4de0-ae43-0b363c4938af', + }, + + noSuchReplyTarget: { + message: 'No such reply target.', + code: 'NO_SUCH_REPLY_TARGET', + id: '749ee0f6-d3da-459a-bf02-282e2da4292c', + }, + + cannotReplyToInvisibleNote: { + message: 'You cannot reply to an invisible Note.', + code: 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE', + id: 'b98980fa-3780-406c-a935-b6d0eeee10d1', + }, + + cannotReplyToPureRenote: { + message: 'You can not reply to a pure Renote.', + code: 'CANNOT_REPLY_TO_A_PURE_RENOTE', + id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', + }, + + cannotReplyToSpecifiedNoteWithExtendedVisibility: { + message: 'You cannot reply to a specified visibility note with extended visibility.', + code: 'CANNOT_REPLY_TO_SPECIFIED_NOTE_WITH_EXTENDED_VISIBILITY', + id: 'ed940410-535c-4d5e-bfa3-af798671e93c', + }, + + cannotCreateAlreadyExpiredPoll: { + message: 'Poll is already expired.', + code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', + id: '04da457d-b083-4055-9082-955525eda5a5', + }, + + noSuchChannel: { + message: 'No such channel.', + code: 'NO_SUCH_CHANNEL', + id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb', + }, + + youHaveBeenBlocked: { + message: 'You have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', + }, + + noSuchFile: { + message: 'Some files are not found.', + code: 'NO_SUCH_FILE', + id: 'b6992544-63e7-67f0-fa7f-32444b1b5306', + }, + + cannotRenoteOutsideOfChannel: { + message: 'Cannot renote outside of channel.', + code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL', + id: '33510210-8452-094c-6227-4a6c05d99f00', + }, + + containsProhibitedWords: { + message: 'Cannot post because it contains prohibited words.', + code: 'CONTAINS_PROHIBITED_WORDS', + id: 'aa6e01d3-a85c-669d-758a-76aab43af334', + }, + + containsTooManyMentions: { + message: 'Cannot post because it exceeds the allowed number of mentions.', + code: 'CONTAINS_TOO_MANY_MENTIONS', + id: '4de0363a-3046-481b-9b0f-feff3e211025', + }, + + noSuchNoteDraft: { + message: 'No such note draft.', + code: 'NO_SUCH_NOTE_DRAFT', + id: '49cd6b9d-848e-41ee-b0b9-adaca711a6b1', + }, + + accessDenied: { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', + }, + + noSuchRenote: { + message: 'No such renote.', + code: 'NO_SUCH_RENOTE', + id: '64929870-2540-4d11-af41-3b484d78c956', + }, + + cannotRenote: { + message: 'Cannot renote.', + code: 'CANNOT_RENOTE', + id: '76cc5583-5a14-4ad3-8717-0298507e32db', + }, + + cannotRenoteToExternal: { + message: 'Cannot Renote to External.', + code: 'CANNOT_RENOTE_TO_EXTERNAL', + id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7', + }, + + noSuchReply: { + message: 'No such reply.', + code: 'NO_SUCH_REPLY', + id: 'c4721841-22fc-4bb7-ad3d-897ef1d375b5', + }, + + cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: { + message: 'You cannot reply to a specified visibility note with extended visibility.', + code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', + id: '215dbc76-336c-4d2a-9605-95766ba7dab0', + }, + + tooManyScheduledNotes: { + message: 'You cannot create scheduled notes any more.', + code: 'TOO_MANY_SCHEDULED_NOTES', + id: '02f5df79-08ae-4a33-8524-f1503c8f6212', + }, + + scheduledAtRequired: { + message: 'scheduledAt is required when isActuallyScheduled is true.', + code: 'SCHEDULED_AT_REQUIRED', + id: 'fe9737d5-cc41-498c-af9d-149207307530', + }, + + scheduledAtMustBeInFuture: { + message: 'scheduledAt must be in the future.', + code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE', + id: 'ed1a6673-d0d1-4364-aaae-9bf3f139cbc5', + }, + }, + + limit: { + duration: ms('1hour'), + max: 300, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + draftId: { type: 'string', nullable: false, format: 'misskey:id' }, + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'] }, + visibleUserIds: { type: 'array', uniqueItems: true, items: { + type: 'string', format: 'misskey:id', + } }, + cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 }, + hashtag: { type: 'string', nullable: true, maxLength: 200 }, + localOnly: { type: 'boolean' }, + reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'] }, + replyId: { type: 'string', format: 'misskey:id', nullable: true }, + renoteId: { type: 'string', format: 'misskey:id', nullable: true }, + channelId: { type: 'string', format: 'misskey:id', nullable: true }, + + // anyOf内にバリデーションを書いても最初の一つしかチェックされない + // See https://github.com/misskey-dev/misskey/pull/10082 + text: { + type: 'string', + minLength: 0, + maxLength: MAX_NOTE_TEXT_LENGTH, + nullable: true, + }, + fileIds: { + type: 'array', + uniqueItems: true, + minItems: 0, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, + }, + poll: { + type: 'object', + nullable: true, + properties: { + choices: { + type: 'array', + uniqueItems: true, + minItems: 0, + maxItems: 10, + items: { type: 'string', minLength: 1, maxLength: 50 }, + }, + multiple: { type: 'boolean' }, + expiresAt: { type: 'integer', nullable: true }, + expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, + }, + required: ['choices'], + }, + scheduledAt: { type: 'integer', nullable: true }, + isActuallyScheduled: { type: 'boolean' }, + }, + required: ['draftId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private noteDraftService: NoteDraftService, + private noteDraftEntityService: NoteDraftEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const draft = await this.noteDraftService.update(me, ps.draftId, { + fileIds: ps.fileIds, + pollChoices: ps.poll?.choices, + pollMultiple: ps.poll?.multiple, + pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null, + pollExpiredAfter: ps.poll?.expiredAfter, + text: ps.text, + replyId: ps.replyId, + renoteId: ps.renoteId, + cw: ps.cw, + hashtag: ps.hashtag, + localOnly: ps.localOnly, + reactionAcceptance: ps.reactionAcceptance, + visibility: ps.visibility, + visibleUserIds: ps.visibleUserIds, + channelId: ps.channelId, + scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, + isActuallyScheduled: ps.isActuallyScheduled, + }).catch((err) => { + if (err instanceof IdentifiableError) { + switch (err.id) { + case '49cd6b9d-848e-41ee-b0b9-adaca711a6b1': + throw new ApiError(meta.errors.noSuchNoteDraft); + case '04da457d-b083-4055-9082-955525eda5a5': + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + case 'b6992544-63e7-67f0-fa7f-32444b1b5306': + throw new ApiError(meta.errors.noSuchFile); + case '64929870-2540-4d11-af41-3b484d78c956': + throw new ApiError(meta.errors.noSuchRenote); + case '76cc5583-5a14-4ad3-8717-0298507e32db': + throw new ApiError(meta.errors.cannotRenote); + case '075ca298-e6e7-485a-b570-51a128bb5168': + throw new ApiError(meta.errors.youHaveBeenBlocked); + case '81eb8188-aea1-4e35-9a8f-3334a3be9855': + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + case '6815399a-6f13-4069-b60d-ed5156249d12': + throw new ApiError(meta.errors.noSuchChannel); + case 'ed1952ac-2d26-4957-8b30-2deda76bedf7': + throw new ApiError(meta.errors.cannotRenoteToExternal); + case 'c4721841-22fc-4bb7-ad3d-897ef1d375b5': + throw new ApiError(meta.errors.noSuchReply); + case 'e6c10b57-2c09-4da3-bd4d-eda05d51d140': + throw new ApiError(meta.errors.cannotReplyToPureRenote); + case '593c323c-6b6a-4501-a25c-2f36bd2a93d6': + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); + case '215dbc76-336c-4d2a-9605-95766ba7dab0': + throw new ApiError(meta.errors.cannotReplyToSpecifiedNoteWithExtendedVisibility); + case 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4': + throw new ApiError(meta.errors.noSuchRenoteTarget); + case 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a': + throw new ApiError(meta.errors.cannotReRenote); + case '749ee0f6-d3da-459a-bf02-282e2da4292c': + throw new ApiError(meta.errors.noSuchReplyTarget); + case '33510210-8452-094c-6227-4a6c05d99f00': + throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel); + case 'aa6e01d3-a85c-669d-758a-76aab43af334': + throw new ApiError(meta.errors.containsProhibitedWords); + case '4de0363a-3046-481b-9b0f-feff3e211025': + throw new ApiError(meta.errors.containsTooManyMentions); + case 'bacdf856-5c51-4159-b88a-804fa5103be5': + throw new ApiError(meta.errors.tooManyScheduledNotes); + case '94a89a43-3591-400a-9c17-dd166e71fdfa': + throw new ApiError(meta.errors.scheduledAtRequired); + case 'b34d0c1b-996f-4e34-a428-c636d98df457': + throw new ApiError(meta.errors.scheduledAtMustBeInFuture); + default: + throw err; + } + } + throw err; + }); + + const updatedDraft = await this.noteDraftEntityService.pack(draft, me); + + return { + updatedDraft, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index dcd971360d..a57c84d432 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { CacheService } from '@/core/CacheService.js'; +import { QueryService } from '@/core/QueryService.js'; export const meta = { tags: ['notes'], @@ -52,6 +53,7 @@ export default class extends Endpoint { // eslint- private cacheService: CacheService, private noteEntityService: NoteEntityService, private featuredService: FeaturedService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { let noteIds: string[]; @@ -94,6 +96,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('note.channel', 'channel'); + this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); + const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 8d38bb1c65..7fa8004209 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -78,11 +78,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - if (me) { - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); - this.queryService.generateMutedUserRenotesQueryForNotes(query, me); - } + this.queryService.generateBaseNoteFilteringQuery(query, me); + if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); @@ -94,6 +91,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.where('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } 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 99d1c9f19c..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 @@ -243,8 +251,7 @@ export default class extends Endpoint { // eslint- } this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.includeMyRenotes === false) { 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 97acf2ad39..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); @@ -156,9 +158,20 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQueryForNotes(query, me); - if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); - if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(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/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index bbb63646e9..e775bdb7fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -35,6 +35,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, visibility: { type: 'string' }, }, required: [], @@ -57,14 +59,14 @@ export default class extends Endpoint { // eslint- .select('following.followeeId') .where('following.followerId = :followerId', { followerId: me.id }); - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere(new Brackets(qb => { qb // このmeIdAsListパラメータはqueryServiceのgenerateVisibilityQueryでセットされる .where(':meIdAsList <@ note.mentions') .orWhere(':meIdAsList <@ note.visibleUserIds'); })) // Avoid scanning primary key index - .orderBy('CONCAT(note.id)', 'DESC') + .orderBy('CONCAT(note.id)', (ps.sinceDate || ps.sinceId) ? 'ASC' : 'DESC') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') @@ -72,9 +74,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); this.queryService.generateMutedNoteThreadQuery(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); if (ps.visibility) { query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 97b12ab7f7..e5e15fd4b3 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -47,6 +47,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['noteId'], } as const; @@ -61,7 +63,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('reaction.noteId = :noteId', { noteId: ps.noteId }) .leftJoinAndSelect('reaction.user', 'user') .leftJoinAndSelect('reaction.note', 'note'); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index b34d9261a1..b294794de6 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -43,6 +43,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['noteId'], } as const; @@ -63,7 +65,7 @@ export default class extends Endpoint { // eslint- throw err; }); - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') @@ -72,8 +74,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQueryForNotes(query, me); - if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); const renotes = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index f36af1a328..d567d7c64a 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -32,6 +32,8 @@ export const paramDef = { noteId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['noteId'], @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') @@ -56,8 +58,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQueryForNotes(query, me); - if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index c45851548a..2cd6610cf6 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -28,38 +28,55 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - reply: { type: 'boolean', nullable: true, default: null }, - renote: { type: 'boolean', nullable: true, default: null }, - withFiles: { - type: 'boolean', - default: false, - description: 'Only show notes that have attached files.', - }, - poll: { type: 'boolean', nullable: true, default: null }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - - tag: { type: 'string', minLength: 1 }, - query: { - type: 'array', - description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', - items: { - type: 'array', - items: { - type: 'string', - minLength: 1, + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + tag: { type: 'string', minLength: 1 }, + }, + required: ['tag'], }, - minItems: 1, - }, - minItems: 1, + { + type: 'object', + properties: { + query: { + type: 'array', + description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', + items: { + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + minItems: 1, + }, + minItems: 1, + }, + }, + required: ['query'], + }, + ], + }, + { + type: 'object', + properties: { + reply: { type: 'boolean', nullable: true, default: null }, + renote: { type: 'boolean', nullable: true, default: null }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, + poll: { type: 'boolean', nullable: true, default: null }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + }, }, - }, - anyOf: [ - { required: ['tag'] }, - { required: ['query'] }, ], } as const; @@ -73,7 +90,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') @@ -81,16 +98,15 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQueryForNotes(query, me); - if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); try { - if (ps.tag) { + if ('tag' in ps) { if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection'); query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] }); } else { query.andWhere(new Brackets(qb => { - for (const tags of ps.query!) { + for (const tags of ps.query) { qb.orWhere(new Brackets(qb => { for (const tag of tags) { if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection'); diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 3fe19806e3..ab1bd934fb 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { SearchService } from '@/core/SearchService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -40,6 +41,8 @@ export const paramDef = { query: { type: 'string' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, host: { @@ -60,8 +63,12 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private searchService: SearchService, private roleService: RoleService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : undefined); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : undefined); + const policies = await this.roleService.getUserPolicies(me ? me.id : null); if (!policies.canSearchNotes) { throw new ApiError(meta.errors.unavailable); @@ -72,8 +79,8 @@ export default class extends Endpoint { // eslint- channelId: ps.channelId, host: ps.host, }, { - untilId: ps.untilId, - sinceId: ps.sinceId, + untilId: untilId, + sinceId: sinceId, limit: ps.limit, }); diff --git a/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts new file mode 100644 index 0000000000..e102bc1d4a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts @@ -0,0 +1,66 @@ +/* + * 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 { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + reactions: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + type: 'number', + }, + }, + reactionEmojis: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + type: 'string', + }, + }, + }, + }, + }, + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + noteIds: { type: 'array', items: { type: 'string', format: 'misskey:id' }, maxItems: 100, minItems: 1 }, + }, + required: ['noteIds'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.noteEntityService.fetchDiffs(ps.noteIds); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 11839bce36..a41de25ddf 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -3,10 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { MiMeta } from '@/models/Meta.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -27,10 +29,16 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, - signinRequired: { - message: 'Signin required.', - code: 'SIGNIN_REQUIRED', - id: '8e75455b-738c-471d-9f80-62693f33372e', + contentRestrictedByUser: { + message: 'Content restricted by user. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_USER', + id: 'fbcc002d-37d9-4944-a6b0-d9e29f2d33ab', + }, + + contentRestrictedByServer: { + message: 'Content restricted by server settings. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_SERVER', + id: '145f88d2-b03d-4087-8143-a78928883c4b', }, }, } as const; @@ -46,17 +54,28 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private noteEntityService: NoteEntityService, private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { - const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => { + const note = await this.getterService.getNoteWithRelations(ps.noteId).catch(err => { if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw err; }); if (note.user!.requireSigninToViewContents && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByUser); + } + + if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { + throw new ApiError(meta.errors.contentRestrictedByServer); + } + + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && note.userHost != null && me == null) { + throw new ApiError(meta.errors.contentRestrictedByServer); } return await this.noteEntityService.pack(note, me, { diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 29c6aa7434..7c0dddb827 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -59,7 +59,7 @@ export default class extends Endpoint { // eslint- throw err; }); - const mutedNotes = await this.notesRepository.find({ + const _mutedNotes = await this.notesRepository.find({ where: [{ id: note.threadId ?? note.id, }, { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index a88b28892e..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 => { @@ -199,8 +211,7 @@ export default class extends Endpoint { // eslint- })); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.includeMyRenotes === false) { @@ -238,7 +249,14 @@ export default class extends Endpoint { // eslint- } if (ps.withRenotes === false) { - query.andWhere('note.renoteId IS NULL'); + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere(new Brackets(qb => { + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + })); } //#endregion diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index e9a6a36b02..cd7d46007c 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -95,7 +95,6 @@ export default class extends Endpoint { // eslint- if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; const params = new URLSearchParams(); - params.append('auth_key', this.serverSettings.deeplAuthKey); params.append('text', note.text); params.append('target_lang', targetLang); @@ -104,6 +103,7 @@ export default class extends Endpoint { // eslint- const res = await this.httpRequestService.send(endpoint, { method: 'POST', headers: { + 'Authorization': `DeepL-Auth-Key ${this.serverSettings.deeplAuthKey}`, 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, */*', }, 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 80f1c69b25..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); @@ -184,10 +186,20 @@ export default class extends Endpoint { // eslint- })); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); + 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 }); @@ -224,6 +236,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 6de5fe3d44..96bc2a953a 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -5,12 +5,13 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; -import { IdService } from '@/core/IdService.js'; -import { MiPage, pageNameSchema } from '@/models/Page.js'; +import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js'; +import { pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; +import { PageService } from '@/core/PageService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -77,11 +78,11 @@ export default class extends Endpoint { // eslint- @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private pageService: PageService, private pageEntityService: PageEntityService, - private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - let eyeCatchingImage = null; + let eyeCatchingImage: MiDriveFile | null = null; if (ps.eyeCatchingImageId != null) { eyeCatchingImage = await this.driveFilesRepository.findOneBy({ id: ps.eyeCatchingImageId, @@ -102,24 +103,20 @@ export default class extends Endpoint { // eslint- } }); - const page = await this.pagesRepository.insertOne(new MiPage({ - id: this.idService.gen(), - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, - userId: me.id, - visibility: 'public', - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - })); + try { + const page = await this.pageService.create(me, { + ...ps, + eyeCatchingImage, + summary: ps.summary ?? null, + }); - return await this.pageEntityService.pack(page); + return await this.pageEntityService.pack(page); + } catch (err) { + if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') { + throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index f2bc946788..a33868552d 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -4,12 +4,14 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, UsersRepository } from '@/models/_.js'; +import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -44,36 +46,17 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private moderationLogService: ModerationLogService, - private roleService: RoleService, + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - - if (!await this.roleService.isModerator(me) && page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } - - await this.pagesRepository.delete(page.id); - - if (page.userId !== me.id) { - const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); - this.moderationLogService.log(me, 'deletePage', { - pageId: page.id, - pageUserId: page.userId, - pageUserUsername: user.username, - page, - }); + try { + await this.pageService.delete(me, ps.pageId); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + } + throw err; } }); } diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index e08b832a3f..8427bab2d5 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -33,15 +33,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - pageId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string' }, - username: { type: 'string' }, - }, anyOf: [ - { required: ['pageId'] }, - { required: ['name', 'username'] }, + { + type: 'object', + properties: { + pageId: { type: 'string', format: 'misskey:id' }, + }, + required: ['pageId'], + }, + { + type: 'object', + properties: { + name: { type: 'string' }, + username: { type: 'string' }, + }, + required: ['name', 'username'], + }, ], } as const; @@ -59,9 +66,9 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { let page: MiPage | null = null; - if (ps.pageId) { + if ('pageId' in ps) { page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - } else if (ps.name && ps.username) { + } else { const author = await this.usersRepository.findOneBy({ host: IsNull(), usernameLower: ps.username.toLowerCase(), diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index a6aeb6002e..6fa5c1d75c 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -4,13 +4,14 @@ */ import ms from 'ms'; -import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { pageNameSchema } from '@/models/Page.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -75,57 +76,37 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - if (page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } + try { + let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId; + if (eyeCatchingImage != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: eyeCatchingImage, + userId: me.id, + }); - if (ps.eyeCatchingImageId != null) { - const eyeCatchingImage = await this.driveFilesRepository.findOneBy({ - id: ps.eyeCatchingImageId, - userId: me.id, - }); - - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - if (ps.name != null) { - await this.pagesRepository.findBy({ - id: Not(ps.pageId), - userId: me.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); } - }); - } + } - await this.pagesRepository.update(page.id, { - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary === undefined ? page.summary : ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - eyeCatchingImageId: ps.eyeCatchingImageId, - }); + await this.pageService.update(me, ps.pageId, { + ...ps, + eyeCatchingImage, + }); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/list.ts b/packages/backend/src/server/api/endpoints/renote-mute/list.ts index 3be01f989a..adf5aa76bf 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/list.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/list.ts @@ -34,6 +34,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -48,7 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.renoteMutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.renoteMutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('muting.muterId = :meId', { meId: me.id }); const mutings = await query diff --git a/packages/backend/src/server/api/endpoints/reversi/games.ts b/packages/backend/src/server/api/endpoints/reversi/games.ts index 6b06068727..22f3a0617b 100644 --- a/packages/backend/src/server/api/endpoints/reversi/games.ts +++ b/packages/backend/src/server/api/endpoints/reversi/games.ts @@ -27,6 +27,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, my: { type: 'boolean', default: false }, }, required: [], @@ -42,7 +44,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .innerJoinAndSelect('game.user1', 'user1') .innerJoinAndSelect('game.user2', 'user2'); diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 6cd9f80929..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,9 +104,23 @@ 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.generateMutedUserQueryForNotes(query, me); - this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBaseNoteFilteringQuery(query, me); const notes = await query.getMany(); notes.sort((a, b) => a.id > b.id ? -1 : 1); diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index 48d350af59..c7ec7910d6 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -51,6 +51,8 @@ export const paramDef = { roleId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, required: ['roleId'], @@ -79,7 +81,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRole); } - const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('assign.roleId = :roleId', { roleId: role.id }) .andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 8301c85f2e..0e8dc73ad9 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -4,7 +4,6 @@ */ import * as os from 'node:os'; -import si from 'systeminformation'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MiMeta } from '@/models/_.js'; @@ -93,6 +92,8 @@ export default class extends Endpoint { // eslint- }, }; + const si = await import('systeminformation'); + const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/users/achievements.ts b/packages/backend/src/server/api/endpoints/users/achievements.ts index f7139b3684..bae216e347 100644 --- a/packages/backend/src/server/api/endpoints/users/achievements.ts +++ b/packages/backend/src/server/api/endpoints/users/achievements.ts @@ -14,15 +14,7 @@ export const meta = { res: { type: 'array', items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - unlockedAt: { - type: 'number', - }, - }, + ref: 'Achievement', }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 7f7d2ea8cc..fb5a2391f2 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -33,6 +33,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['userId'], } as const; @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('clip.userId = :userId', { userId: ps.userId }) .andWhere('clip.isPublic = true'); diff --git a/packages/backend/src/server/api/endpoints/users/featured-notes.ts b/packages/backend/src/server/api/endpoints/users/featured-notes.ts index e01f19ba7a..90bd11bc25 100644 --- a/packages/backend/src/server/api/endpoints/users/featured-notes.ts +++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts @@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { CacheService } from '@/core/CacheService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; +import { QueryService } from '@/core/QueryService.js'; export const meta = { tags: ['notes'], @@ -49,6 +50,7 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private featuredService: FeaturedService, private cacheService: CacheService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set(); @@ -85,6 +87,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('note.channel', 'channel'); + this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); + const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe, false)) return false; if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false; diff --git a/packages/backend/src/server/api/endpoints/users/flashs.ts b/packages/backend/src/server/api/endpoints/users/flashs.ts index e5ea450215..2ef514bebf 100644 --- a/packages/backend/src/server/api/endpoints/users/flashs.ts +++ b/packages/backend/src/server/api/endpoints/users/flashs.ts @@ -33,11 +33,12 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['userId'], } as const; - -// eslint-disable-next-line import/no-default-export + @Injectable() export default class extends Endpoint { constructor( @@ -48,7 +49,7 @@ export default class extends Endpoint { private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('flash.userId = :userId', { userId: ps.userId }) .andWhere('flash.visibility = \'public\''); diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index a8b4319a61..84c4c80d01 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -47,23 +47,40 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - - userId: { type: 'string', format: 'misskey:id' }, - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + type: 'object', + properties: { + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, + required: ['username', 'host'], + }, + ], + }, + { + type: 'object', + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + }, }, - }, - anyOf: [ - { required: ['userId'] }, - { required: ['username', 'host'] }, ], } as const; @@ -85,9 +102,9 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const user = await this.usersRepository.findOneBy(ps.userId != null + const user = await this.usersRepository.findOneBy('userId' in ps ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -117,7 +134,7 @@ export default class extends Endpoint { // eslint- } } - const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('following.followeeId = :userId', { userId: user.id }) .innerJoinAndSelect('following.follower', 'follower'); diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index feda5bb353..3b3352858f 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -54,25 +54,41 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - - userId: { type: 'string', format: 'misskey:id' }, - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + type: 'object', + properties: { + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, + required: ['username', 'host'], + }, + ], + }, + { + type: 'object', + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + birthday: { ...birthdaySchema, nullable: true, description: '@deprecated use get-following-birthday-users instead.' }, + }, }, - - birthday: { ...birthdaySchema, nullable: true }, - }, - anyOf: [ - { required: ['userId'] }, - { required: ['username', 'host'] }, ], } as const; @@ -94,9 +110,9 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const user = await this.usersRepository.findOneBy(ps.userId != null + const user = await this.usersRepository.findOneBy('userId' in ps ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -126,19 +142,20 @@ export default class extends Endpoint { // eslint- } } - const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('following.followerId = :userId', { userId: user.id }) .innerJoinAndSelect('following.followee', 'followee'); + // @deprecated use get-following-birthday-users instead. if (ps.birthday) { - try { - const birthday = ps.birthday.substring(5, 10); - const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile'); - birthdayUserQuery.select('user_profile.userId') - .where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`); + query.innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); - query.andWhere(`following.followeeId IN (${ birthdayUserQuery.getQuery() })`); - } catch (err) { + try { + const birthday = ps.birthday.split('-'); + birthday.shift(); // 年の部分を削除 + // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: parseInt(birthday.join('')) }); + } catch (_) { throw new ApiError(meta.errors.birthdayInvalid); } } diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 553886374c..55e885e3c3 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -33,6 +33,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['userId'], } as const; @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('post.userId = :userId', { userId: ps.userId }); const posts = await query diff --git a/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts b/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts new file mode 100644 index 0000000000..124114244e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { + FollowingsRepository, + UserProfilesRepository, +} from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type { Packed } from '@/misc/json-schema.js'; + +export const meta = { + tags: ['users'], + + requireCredential: true, + kind: 'read:account', + + description: 'Find users who have a birthday on the specified range.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'misskey:id', + }, + birthday: { + type: 'string', + optional: false, nullable: false, + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + offset: { type: 'integer', default: 0 }, + birthday: { + oneOf: [{ + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, { + type: 'object', + properties: { + begin: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + end: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + }, + required: ['begin', 'end'], + }], + }, + }, + required: ['birthday'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.followingsRepository + .createQueryBuilder('following') + .andWhere('following.followerId = :userId', { userId: me.id }) + .innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); + + if (Object.hasOwn(ps.birthday, 'begin') && Object.hasOwn(ps.birthday, 'end')) { + const range = ps.birthday as { begin: { month: number; day: number }; end: { month: number; day: number }; }; + + // 誕生日は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)でインデックスが効くようになっているので、その形式に変換 + const begin = range.begin.month * 100 + range.begin.day; + const end = range.end.month * 100 + range.end.day; + + if (begin <= end) { + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND :end', { begin, end }); + } else { + // 12/31 から 1/1 の範囲を取得するために OR で対応 + query.andWhere(new Brackets(qb => { + qb.where('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND 1231', { begin }); + qb.orWhere('get_birthday_date(followeeProfile.birthday) BETWEEN 101 AND :end', { end }); + })); + } + } else { + const { month, day } = ps.birthday as { month: number; day: number }; + // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: month * 100 + day }); + } + + query.select('following.followeeId', 'user_id'); + query.addSelect('get_birthday_date(followeeProfile.birthday)', 'birthday_date'); + query.orderBy('birthday_date', 'ASC'); + + const birthdayUsers = await query + .offset(ps.offset).limit(ps.limit) + .getRawMany<{ birthday_date: number; user_id: string }>(); + + const users = new Map>(( + await this.userEntityService.packMany( + birthdayUsers.map(u => u.user_id), + me, + { schema: 'UserLite' }, + ) + ).map(u => [u.id, u])); + + return birthdayUsers + .map(item => { + const birthday = new Date(); + birthday.setHours(0, 0, 0, 0); + // item.birthday_date は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)で出力されるので、日付に戻してDateオブジェクトに設定 + birthday.setMonth(Math.floor(item.birthday_date / 100) - 1, item.birthday_date % 100); + + if (birthday.getTime() < new Date().setHours(0, 0, 0, 0)) { + birthday.setFullYear(new Date().getFullYear() + 1); + } + + const birthdayStr = `${birthday.getFullYear()}-${(birthday.getMonth() + 1).toString().padStart(2, '0')}-${(birthday.getDate()).toString().padStart(2, '0')}`; + return { + id: item.user_id, + birthday: birthdayStr, + user: users.get(item.user_id), + }; + }) + .filter(item => item.user != null) + .map(item => item as { id: string; birthday: string; user: Packed<'UserLite'> }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts index 6d6e8d34ea..a841c4b94d 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts @@ -64,6 +64,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['listId'], } as const; @@ -94,7 +96,7 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchList); } - const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('membership.userListId = :userListId', { userListId: userList.id }) .innerJoinAndSelect('membership.user', 'user'); diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8756801fe4..c6d477a92f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -22,7 +22,26 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'UserList', + allOf: [ + { + type: 'object', + ref: 'UserList', + }, + { + type: 'object', + optional: false, nullable: false, + properties: { + likedCount: { + type: 'number', + optional: true, nullable: false, + }, + isLiked: { + type: 'boolean', + optional: true, nullable: false, + }, + }, + }, + ], }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index f5b7a07b01..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); @@ -129,6 +130,8 @@ export default class extends Endpoint { // eslint- redisTimelines, useDbFallback: true, ignoreAuthorFromMute: true, + ignoreAuthorFromInstanceBlock: true, + ignoreAuthorFromUserSuspension: true, excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files excludePureRenotes: !ps.withRenotes, @@ -163,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) @@ -175,20 +183,36 @@ 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'); } - this.queryService.generateVisibilityQuery(query, me); - if (me) { - this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId }); - this.queryService.generateBlockedUserQueryForNotes(query, me); + // -- ミュートされたチャンネルのリノート対策 + 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, + excludeUserFromMute: ps.userId, + }); + if (ps.withFiles) { query.andWhere('note.fileIds != \'{}\''); } diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index bb7de0e0b5..1d7499d4be 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -33,6 +33,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: ['userId'], } as const; @@ -47,7 +49,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('page.userId = :userId', { userId: ps.userId }) .andWhere('page.visibility = \'public\''); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 7805ae3288..d84a191f7a 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -28,7 +28,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'NoteReaction', + ref: 'NoteReactionWithNote', }, }, @@ -99,9 +99,16 @@ export default class extends Endpoint { // eslint- const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('reaction.userId = :userId', { userId: ps.userId }) - .leftJoinAndSelect('reaction.note', 'note'); + .leftJoinAndSelect('reaction.note', 'note') + .leftJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const reactions = (await query .limit(ps.limit) @@ -113,7 +120,7 @@ export default class extends Endpoint { // eslint- return true; }); - return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); + return await this.noteReactionEntityService.packManyWithNote(reactions, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 5b1c6b514b..769a72d7a1 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -64,6 +64,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateMutedUserQueryForUsers(query, me); this.queryService.generateBlockQueryForUsers(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' }); const followingQuery = this.followingsRepository.createQueryBuilder('following') .select('following.followeeId') diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 1d75437b81..f146095cf1 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -114,7 +114,7 @@ export const paramDef = { type: 'object', properties: { userId: { - anyOf: [ + oneOf: [ { type: 'string', format: 'misskey:id' }, { type: 'array', diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 134f1a8e87..d1d6354d53 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -26,17 +26,32 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - detail: { type: 'boolean', default: true }, - - username: { type: 'string', nullable: true }, - host: { type: 'string', nullable: true }, - }, - anyOf: [ - { required: ['username'] }, - { required: ['host'] }, + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + username: { type: 'string', nullable: true }, + }, + required: ['username'], + }, + { + type: 'object', + properties: { + host: { type: 'string', nullable: true }, + }, + required: ['host'], + }, + ], + }, + { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + detail: { type: 'boolean', default: true }, + }, + }, ], } as const; @@ -47,8 +62,8 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, (ps, me) => { return this.userSearchService.searchByUsernameAndHost({ - username: ps.username, - host: ps.host, + username: 'username' in ps ? ps.username : undefined, + host: 'host' in ps ? ps.host : undefined, }, { limit: ps.limit, detail: ps.detail, diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 5d36847e03..c422286152 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['users'], requireCredential: false, + requiredRolePolicy: 'canSearchUsers', description: 'Search for users.', diff --git a/packages/backend/src/server/api/endpoints/users/show.test.ts b/packages/backend/src/server/api/endpoints/users/show.test.ts new file mode 100644 index 0000000000..068ffd8bc9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/show.test.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import { getValidator } from '../../../../../test/prelude/get-api-validator.js'; +import { paramDef } from './show.js'; + +const VALID = true; +const INVALID = false; + +describe('api:users/show', () => { + describe('validation', () => { + const v = getValidator(paramDef); + + test('Reject empty', () => expect(v({})).toBe(INVALID)); + test('Reject host only', () => expect(v({ host: 'misskey.test' })).toBe(INVALID)); + test('Accept userId only', () => expect(v({ userId: '1' })).toBe(VALID)); + test('Accept username and host', () => expect(v({ username: 'alice', host: 'misskey.test' })).toBe(VALID)); + }); +}); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 062326e28d..5ff3a63d6a 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -5,7 +5,7 @@ import { In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsersRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -59,29 +59,53 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - userId: { type: 'string', format: 'misskey:id' }, - userIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', + allOf: [ + { + anyOf: [ + { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + type: 'object', + properties: { + userIds: { type: 'array', uniqueItems: true, items: { + type: 'string', format: 'misskey:id', + } }, + }, + required: ['userIds'], + }, + { + type: 'object', + properties: { + username: { type: 'string' }, + }, + required: ['username'], + }, + ], + }, + { + type: 'object', + properties: { + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, }, - }, - anyOf: [ - { required: ['userId'] }, - { required: ['userIds'] }, - { required: ['username'] }, ], } as const; @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -92,12 +116,19 @@ export default class extends Endpoint { // eslint- private apiLoggerService: ApiLoggerService, ) { super(meta, paramDef, async (ps, me, _1, _2, _3, ip) => { + // ログイン時にusers/showできなくなってしまう + //if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { + // throw new ApiError(meta.errors.noSuchUser); + //} + let user; const isModerator = await this.roleService.isModerator(me); - ps.username = ps.username?.trim(); + if ('username' in ps) { + ps.username = ps.username.trim(); + } - if (ps.userIds) { + if ('userIds' in ps) { if (ps.userIds.length === 0) { return []; } @@ -122,13 +153,17 @@ export default class extends Endpoint { // eslint- return _users.map(u => _userMap.get(u.id)!); } else { // Lookup user - if (typeof ps.host === 'string' && typeof ps.username === 'string') { + if (typeof ps.host === 'string' && 'username' in ps) { + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) { + throw new ApiError(meta.errors.noSuchUser); + } + user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { - const q: FindOptionsWhere = ps.userId != null + const q: FindOptionsWhere = 'userId' in ps ? { id: ps.userId } : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; @@ -139,6 +174,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchUser); } + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && user.host != null && me == null) { + throw new ApiError(meta.errors.noSuchUser); + } + if (user.host == null) { if (me == null && ip != null) { this.perUserPvChart.commitByVisitor(user, ip); diff --git a/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts index 7139715293..c07d100a99 100644 --- a/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts @@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { CustomEmojiService, fetchEmojisHostTypes, fetchEmojisSortKeys } from '@/core/CustomEmojiService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -65,6 +66,8 @@ export const paramDef = { }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, page: { type: 'integer' }, sortKeys: { @@ -84,8 +87,12 @@ export default class extends Endpoint { // eslint- constructor( private customEmojiService: CustomEmojiService, private emojiEntityService: EmojiEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : undefined); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : undefined); + const q = ps.query; const result = await this.customEmojiService.fetchEmojis( { @@ -105,8 +112,8 @@ export default class extends Endpoint { // eslint- hostType: q?.hostType, roleIds: q?.roleIds, }, - sinceId: ps.sinceId, - untilId: ps.untilId, + sinceId: sinceId, + untilId: untilId, }, { limit: ps.limit, diff --git a/packages/backend/src/server/api/endpoints/verify-email.ts b/packages/backend/src/server/api/endpoints/verify-email.ts new file mode 100644 index 0000000000..e069ed59f2 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/verify-email.ts @@ -0,0 +1,66 @@ +/* + * 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 { UserProfilesRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ApiError } from '../error.js'; + +export const meta = { + requireCredential: false, + + tags: ['account'], + + errors: { + noSuchCode: { + message: 'No such code.', + code: 'NO_SUCH_CODE', + id: '97c1f576-e4b8-4b8a-a6dc-9cb65e7f6f85', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + code: { type: 'string' }, + }, + required: ['code'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const profile = await this.userProfilesRepository.findOneBy({ + emailVerifyCode: ps.code, + }); + + if (profile == null) { + throw new ApiError(meta.errors.noSuchCode); + } + + await this.userProfilesRepository.update({ userId: profile.userId }, { + emailVerified: true, + emailVerifyCode: null, + }); + + this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { + schema: 'MeDetailed', + includeSecrets: true, + })); + }); + } +} + diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index ea64e32ee6..e1dead07cf 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -89,7 +89,8 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) { schema.required = undefined; } - const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1); + const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1) + || ['allOf', 'oneOf', 'anyOf'].some(o => (Array.isArray(schema[o]) && schema[o].length >= 0)); const info = { operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index c80dda8d96..0714f61294 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -9,9 +9,8 @@ import { refs } from '@/misc/json-schema.js'; export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any { // optional, nullable, refはスキーマ定義に含まれないので分離しておく - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { optional, nullable, ref, selfRef, ..._res }: any = schema; - const res = deepClone(_res); + const { optional, nullable, ref, selfRef, ...res1 }: any = schema; + const res = deepClone(res1); if (schema.type === 'object' && schema.properties) { if (type === 'res') { @@ -38,14 +37,13 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're if (type === 'res' && schema.ref && (!schema.selfRef || includeSelfRef)) { const $ref = `#/components/schemas/${schema.ref}`; - if (schema.nullable || schema.optional) { - res.allOf = [{ $ref }]; + if (schema.nullable) { + res.oneOf = [{ $ref }, { type: 'null' }]; } else { res.$ref = $ref; } - } - - if (schema.nullable) { + delete res.type; + } else if (schema.nullable) { if (Array.isArray(schema.type) && !schema.type.includes('null')) { res.type.push('null'); } else if (typeof schema.type === 'string') { diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts index 939e6e2e44..adc7eaa226 100644 --- a/packages/backend/src/server/api/stream/ChannelsService.ts +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -5,74 +5,55 @@ import { Injectable } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; -import { HybridTimelineChannelService } from './channels/hybrid-timeline.js'; -import { LocalTimelineChannelService } from './channels/local-timeline.js'; -import { HomeTimelineChannelService } from './channels/home-timeline.js'; -import { GlobalTimelineChannelService } from './channels/global-timeline.js'; -import { MainChannelService } from './channels/main.js'; -import { ChannelChannelService } from './channels/channel.js'; -import { AdminChannelService } from './channels/admin.js'; -import { ServerStatsChannelService } from './channels/server-stats.js'; -import { QueueStatsChannelService } from './channels/queue-stats.js'; -import { UserListChannelService } from './channels/user-list.js'; -import { AntennaChannelService } from './channels/antenna.js'; -import { DriveChannelService } from './channels/drive.js'; -import { HashtagChannelService } from './channels/hashtag.js'; -import { RoleTimelineChannelService } from './channels/role-timeline.js'; -import { ChatUserChannelService } from './channels/chat-user.js'; -import { ChatRoomChannelService } from './channels/chat-room.js'; -import { ReversiChannelService } from './channels/reversi.js'; -import { ReversiGameChannelService } from './channels/reversi-game.js'; -import { MahjongRoomChannelService } from './channels/mahjong-room.js'; -import { type MiChannelService } from './channel.js'; +import { HybridTimelineChannel } from './channels/hybrid-timeline.js'; +import { LocalTimelineChannel } from './channels/local-timeline.js'; +import { HomeTimelineChannel } from './channels/home-timeline.js'; +import { GlobalTimelineChannel } from './channels/global-timeline.js'; +import { MainChannel } from './channels/main.js'; +import { ChannelChannel } from './channels/channel.js'; +import { AdminChannel } from './channels/admin.js'; +import { ServerStatsChannel } from './channels/server-stats.js'; +import { QueueStatsChannel } from './channels/queue-stats.js'; +import { UserListChannel } from './channels/user-list.js'; +import { AntennaChannel } from './channels/antenna.js'; +import { DriveChannel } from './channels/drive.js'; +import { HashtagChannel } from './channels/hashtag.js'; +import { RoleTimelineChannel } from './channels/role-timeline.js'; +import { ChatUserChannel } from './channels/chat-user.js'; +import { ChatRoomChannel } from './channels/chat-room.js'; +import { ReversiChannel } from './channels/reversi.js'; +import { ReversiGameChannel } from './channels/reversi-game.js'; +import { MahjongRoomChannel } from './channels/mahjong-room.js'; +import type { ChannelConstructor } from './channel.js'; @Injectable() export class ChannelsService { constructor( - private mainChannelService: MainChannelService, - private homeTimelineChannelService: HomeTimelineChannelService, - private localTimelineChannelService: LocalTimelineChannelService, - private hybridTimelineChannelService: HybridTimelineChannelService, - private globalTimelineChannelService: GlobalTimelineChannelService, - private userListChannelService: UserListChannelService, - private hashtagChannelService: HashtagChannelService, - private roleTimelineChannelService: RoleTimelineChannelService, - private antennaChannelService: AntennaChannelService, - private channelChannelService: ChannelChannelService, - private driveChannelService: DriveChannelService, - private serverStatsChannelService: ServerStatsChannelService, - private queueStatsChannelService: QueueStatsChannelService, - private adminChannelService: AdminChannelService, - private chatUserChannelService: ChatUserChannelService, - private chatRoomChannelService: ChatRoomChannelService, - private reversiChannelService: ReversiChannelService, - private reversiGameChannelService: ReversiGameChannelService, - private mahjongRoomChannelService: MahjongRoomChannelService, ) { } @bindThis - public getChannelService(name: string): MiChannelService { + public getChannelConstructor(name: string): ChannelConstructor { switch (name) { - case 'main': return this.mainChannelService; - case 'homeTimeline': return this.homeTimelineChannelService; - case 'localTimeline': return this.localTimelineChannelService; - case 'hybridTimeline': return this.hybridTimelineChannelService; - case 'globalTimeline': return this.globalTimelineChannelService; - case 'userList': return this.userListChannelService; - case 'hashtag': return this.hashtagChannelService; - case 'roleTimeline': return this.roleTimelineChannelService; - case 'antenna': return this.antennaChannelService; - case 'channel': return this.channelChannelService; - case 'drive': return this.driveChannelService; - case 'serverStats': return this.serverStatsChannelService; - case 'queueStats': return this.queueStatsChannelService; - case 'admin': return this.adminChannelService; - case 'chatUser': return this.chatUserChannelService; - case 'chatRoom': return this.chatRoomChannelService; - case 'reversi': return this.reversiChannelService; - case 'reversiGame': return this.reversiGameChannelService; - case 'mahjongRoom': return this.mahjongRoomChannelService; + case 'main': return MainChannel; + case 'homeTimeline': return HomeTimelineChannel; + case 'localTimeline': return LocalTimelineChannel; + case 'hybridTimeline': return HybridTimelineChannel; + case 'globalTimeline': return GlobalTimelineChannel; + case 'userList': return UserListChannel; + case 'hashtag': return HashtagChannel; + case 'roleTimeline': return RoleTimelineChannel; + case 'antenna': return AntennaChannel; + case 'channel': return ChannelChannel; + case 'drive': return DriveChannel; + case 'serverStats': return ServerStatsChannel; + case 'queueStats': return QueueStatsChannel; + case 'admin': return AdminChannel; + case 'chatUser': return ChatUserChannel; + case 'chatRoom': return ChatRoomChannel; + case 'reversi': return ReversiChannel; + case 'reversiGame': return ReversiGameChannel; + case 'mahjongRoom': return MahjongRoomChannel; default: throw new Error(`no such channel: ${name}`); diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index c9801d8314..5989409997 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -6,18 +6,39 @@ import * as WebSocket from 'ws'; import type { MiUser } from '@/models/User.js'; import type { MiAccessToken } from '@/models/AccessToken.js'; -import type { Packed } from '@/misc/json-schema.js'; -import type { NotificationService } from '@/core/NotificationService.js'; +import { 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 { isJsonObject } from '@/misc/json-value.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import type { ChannelsService } from './ChannelsService.js'; +import { isJsonObject } from '@/misc/json-value.js'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; +import type { ChannelConstructor } from './channel.js'; +import type { ChannelRequest } from './channel.js'; +import { ContextIdFactory, ModuleRef, REQUEST } from '@nestjs/core'; +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { MainChannel } from '@/server/api/stream/channels/main.js'; +import { HomeTimelineChannel } from '@/server/api/stream/channels/home-timeline.js'; +import { LocalTimelineChannel } from '@/server/api/stream/channels/local-timeline.js'; +import { HybridTimelineChannel } from '@/server/api/stream/channels/hybrid-timeline.js'; +import { GlobalTimelineChannel } from '@/server/api/stream/channels/global-timeline.js'; +import { UserListChannel } from '@/server/api/stream/channels/user-list.js'; +import { HashtagChannel } from '@/server/api/stream/channels/hashtag.js'; +import { RoleTimelineChannel } from '@/server/api/stream/channels/role-timeline.js'; +import { AntennaChannel } from '@/server/api/stream/channels/antenna.js'; +import { ChannelChannel } from '@/server/api/stream/channels/channel.js'; +import { DriveChannel } from '@/server/api/stream/channels/drive.js'; +import { ServerStatsChannel } from '@/server/api/stream/channels/server-stats.js'; +import { QueueStatsChannel } from '@/server/api/stream/channels/queue-stats.js'; +import { AdminChannel } from '@/server/api/stream/channels/admin.js'; +import { ChatUserChannel } from '@/server/api/stream/channels/chat-user.js'; +import { ChatRoomChannel } from '@/server/api/stream/channels/chat-room.js'; +import { ReversiChannel } from '@/server/api/stream/channels/reversi.js'; +import { ReversiGameChannel } from '@/server/api/stream/channels/reversi-game.js'; const MAX_CHANNELS_PER_CONNECTION = 32; @@ -25,6 +46,7 @@ const MAX_CHANNELS_PER_CONNECTION = 32; * Main stream connection */ // eslint-disable-next-line import/no-default-export +@Injectable({ scope: Scope.TRANSIENT }) export default class Connection { public user?: MiUser; public token?: MiAccessToken; @@ -32,10 +54,10 @@ export default class Connection { public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: Partial> = {}; - private cachedNotes: Packed<'Note'>[] = []; 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(); @@ -43,25 +65,34 @@ export default class Connection { private fetchIntervalId: NodeJS.Timeout | null = null; constructor( - private channelsService: ChannelsService, + private moduleRef: ModuleRef, private notificationService: NotificationService, private cacheService: CacheService, private channelFollowingService: ChannelFollowingService, - - user: MiUser | null | undefined, - token: MiAccessToken | null | undefined, + private channelMutingService: ChannelMutingService, + @Inject(REQUEST) + request: ConnectionRequest, ) { - if (user) this.user = user; - if (token) this.token = token; + if (request.user) this.user = request.user; + if (request.token) this.token = request.token; } @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), @@ -69,6 +100,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; @@ -107,7 +139,7 @@ export default class Connection { try { obj = JSON.parse(data.toString()); - } catch (e) { + } catch (_) { return; } @@ -132,26 +164,6 @@ export default class Connection { this.sendMessageToWs(data.type, data.body); } - @bindThis - public cacheNote(note: Packed<'Note'>) { - const add = (note: Packed<'Note'>) => { - const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); - if (existIndex > -1) { - this.cachedNotes[existIndex] = note; - return; - } - - this.cachedNotes.unshift(note); - if (this.cachedNotes.length > 32) { - this.cachedNotes.splice(32); - } - }; - - add(note); - if (note.reply) add(note.reply); - if (note.renote) add(note.renote); - } - @bindThis private onReadNotification(payload: JsonValue | undefined) { this.notificationService.readAllNotification(this.user!.id); @@ -241,28 +253,34 @@ export default class Connection { * チャンネルに接続 */ @bindThis - public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) { + public async connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) { if (this.channels.length >= MAX_CHANNELS_PER_CONNECTION) { return; } - const channelService = this.channelsService.getChannelService(channel); + const channelConstructor = this.getChannelConstructor(channel); - if (channelService.requireCredential && this.user == null) { + if (channelConstructor.requireCredential && this.user == null) { return; } - if (this.token && ((channelService.kind && !this.token.permission.some(p => p === channelService.kind)) - || (!channelService.kind && channelService.requireCredential))) { + if (this.token && ((channelConstructor.kind && !this.token.permission.some(p => p === channelConstructor.kind)) + || (!channelConstructor.kind && channelConstructor.requireCredential))) { return; } // 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視 - if (channelService.shouldShare && this.channels.some(c => c.chName === channel)) { + if (channelConstructor.shouldShare && this.channels.some(c => c.chName === channel)) { return; } - const ch: Channel = channelService.create(id, this); + const contextId = ContextIdFactory.create(); + this.moduleRef.registerRequestByContextId({ + id: id, + connection: this, + }, contextId); + const ch: Channel = await this.moduleRef.create(channelConstructor, contextId); + this.channels.push(ch); ch.init(params ?? {}); @@ -273,6 +291,33 @@ export default class Connection { } } + @bindThis + public getChannelConstructor(name: string): ChannelConstructor { + switch (name) { + case 'main': return MainChannel; + case 'homeTimeline': return HomeTimelineChannel; + case 'localTimeline': return LocalTimelineChannel; + case 'hybridTimeline': return HybridTimelineChannel; + case 'globalTimeline': return GlobalTimelineChannel; + case 'userList': return UserListChannel; + case 'hashtag': return HashtagChannel; + case 'roleTimeline': return RoleTimelineChannel; + case 'antenna': return AntennaChannel; + case 'channel': return ChannelChannel; + case 'drive': return DriveChannel; + case 'serverStats': return ServerStatsChannel; + case 'queueStats': return QueueStatsChannel; + case 'admin': return AdminChannel; + case 'chatUser': return ChatUserChannel; + case 'chatRoom': return ChatRoomChannel; + case 'reversi': return ReversiChannel; + case 'reversiGame': return ReversiGameChannel; + + default: + throw new Error(`no such channel: ${name}`); + } + } + /** * チャンネルから切断 * @param id チャンネルコネクションID @@ -315,3 +360,8 @@ export default class Connection { } } } + +export interface ConnectionRequest { + user: MiUser | null | undefined, + token: MiAccessToken | null | undefined, +} diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 686aea423c..86b073414d 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'; @@ -21,7 +22,7 @@ export default abstract class Channel { public abstract readonly chName: string; public static readonly shouldShare: boolean; public static readonly requireCredential: boolean; - public static readonly kind?: string | null; + public static readonly kind: string | null; protected get user() { return this.connection.user; @@ -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,12 +79,15 @@ 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; } - constructor(id: string, connection: Connection) { - this.id = id; - this.connection = connection; + constructor(request: ChannelRequest) { + this.id = request.id; + this.connection = request.connection; } public send(payload: { type: string, body: JsonValue }): void; @@ -103,9 +111,14 @@ export default abstract class Channel { public onMessage?(type: string, body: JsonValue): void; } -export type MiChannelService = { +export interface ChannelRequest { + id: string, + connection: Connection, +} + +export interface ChannelConstructor { + new(...args: any[]): Channel; shouldShare: boolean; requireCredential: T; kind: T extends true ? string : string | null | undefined; - create: (id: string, connection: Connection) => Channel; -}; +} diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index 355d5dba21..821888cca0 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -3,17 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class AdminChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class AdminChannel extends Channel { public readonly chName = 'admin'; public static shouldShare = true; public static requireCredential = true as const; public static kind = 'read:admin:stream'; + constructor( + @Inject(REQUEST) + request: ChannelRequest, + ) { + super(request); + } + @bindThis public async init(params: JsonObject) { // Subscribe admin stream @@ -22,22 +31,3 @@ class AdminChannel extends Channel { }); } } - -@Injectable() -export class AdminChannelService implements MiChannelService { - public readonly shouldShare = AdminChannel.shouldShare; - public readonly requireCredential = AdminChannel.requireCredential; - public readonly kind = AdminChannel.kind; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): AdminChannel { - return new AdminChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 53dc7f18b6..ece9d2c8b1 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -3,14 +3,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class AntennaChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class AntennaChannel extends Channel { public readonly chName = 'antenna'; public static shouldShare = false; public static requireCredential = true as const; @@ -18,12 +20,12 @@ class AntennaChannel extends Channel { private antennaId: string; constructor( - private noteEntityService: NoteEntityService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); //this.onEvent = this.onEvent.bind(this); } @@ -43,8 +45,6 @@ class AntennaChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - this.connection.cacheNote(note); - this.send('note', note); } else { this.send(data.type, data.body); @@ -57,24 +57,3 @@ class AntennaChannel extends Channel { this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent); } } - -@Injectable() -export class AntennaChannelService implements MiChannelService { - public readonly shouldShare = AntennaChannel.shouldShare; - public readonly requireCredential = AntennaChannel.requireCredential; - public readonly kind = AntennaChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): AntennaChannel { - return new AntennaChannel( - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 7108e0cd6e..1706b17526 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -3,27 +3,31 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; 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'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class ChannelChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ChannelChannel extends Channel { public readonly chName = 'channel'; public static shouldShare = false; public static requireCredential = false as const; private channelId: string; constructor( - private noteEntityService: NoteEntityService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -40,6 +44,10 @@ class ChannelChannel extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; + if (note.user.requireSigninToViewContents && this.user == null) return; + if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return; + if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return; + if (this.isNoteMutedOrBlocked(note)) return; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { @@ -49,35 +57,41 @@ class ChannelChannel extends Channel { } } - this.connection.cacheNote(note); - 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 this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class ChannelChannelService implements MiChannelService { - public readonly shouldShare = ChannelChannel.shouldShare; - public readonly requireCredential = ChannelChannel.requireCredential; - public readonly kind = ChannelChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ChannelChannel { - return new ChannelChannel( - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/chat-room.ts b/packages/backend/src/server/api/stream/channels/chat-room.ts index eda333dd30..7f949032e2 100644 --- a/packages/backend/src/server/api/stream/channels/chat-room.ts +++ b/packages/backend/src/server/api/stream/channels/chat-room.ts @@ -3,14 +3,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; import { ChatService } from '@/core/ChatService.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class ChatRoomChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ChatRoomChannel extends Channel { public readonly chName = 'chatRoom'; public static shouldShare = false; public static requireCredential = true as const; @@ -18,12 +20,12 @@ class ChatRoomChannel extends Channel { private roomId: string; constructor( - private chatService: ChatService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private chatService: ChatService, ) { - super(id, connection); + super(request); } @bindThis @@ -55,24 +57,3 @@ class ChatRoomChannel extends Channel { this.subscriber.off(`chatRoomStream:${this.roomId}`, this.onEvent); } } - -@Injectable() -export class ChatRoomChannelService implements MiChannelService { - public readonly shouldShare = ChatRoomChannel.shouldShare; - public readonly requireCredential = ChatRoomChannel.requireCredential; - public readonly kind = ChatRoomChannel.kind; - - constructor( - private chatService: ChatService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ChatRoomChannel { - return new ChatRoomChannel( - this.chatService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/chat-user.ts b/packages/backend/src/server/api/stream/channels/chat-user.ts index 5323484ed7..36f3f67b28 100644 --- a/packages/backend/src/server/api/stream/channels/chat-user.ts +++ b/packages/backend/src/server/api/stream/channels/chat-user.ts @@ -3,14 +3,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; import { ChatService } from '@/core/ChatService.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class ChatUserChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ChatUserChannel extends Channel { public readonly chName = 'chatUser'; public static shouldShare = false; public static requireCredential = true as const; @@ -18,12 +20,12 @@ class ChatUserChannel extends Channel { private otherId: string; constructor( - private chatService: ChatService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private chatService: ChatService, ) { - super(id, connection); + super(request); } @bindThis @@ -55,24 +57,3 @@ class ChatUserChannel extends Channel { this.subscriber.off(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent); } } - -@Injectable() -export class ChatUserChannelService implements MiChannelService { - public readonly shouldShare = ChatUserChannel.shouldShare; - public readonly requireCredential = ChatUserChannel.requireCredential; - public readonly kind = ChatUserChannel.kind; - - constructor( - private chatService: ChatService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ChatUserChannel { - return new ChatUserChannel( - this.chatService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 03768f3d23..6f2eb2c8f9 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -3,17 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class DriveChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class DriveChannel extends Channel { public readonly chName = 'drive'; public static shouldShare = true; public static requireCredential = true as const; public static kind = 'read:account'; + constructor( + @Inject(REQUEST) + request: ChannelRequest, + ) { + super(request); + } + @bindThis public async init(params: JsonObject) { // Subscribe drive stream @@ -22,22 +31,3 @@ class DriveChannel extends Channel { }); } } - -@Injectable() -export class DriveChannelService implements MiChannelService { - public readonly shouldShare = DriveChannel.shouldShare; - public readonly requireCredential = DriveChannel.requireCredential; - public readonly kind = DriveChannel.kind; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): DriveChannel { - return new DriveChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 795980821b..be6be1b1e7 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -11,9 +11,11 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class GlobalTimelineChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class GlobalTimelineChannel extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = false; public static requireCredential = false as const; @@ -21,14 +23,14 @@ class GlobalTimelineChannel extends Channel { private withFiles: boolean; constructor( + @Inject(REQUEST) + request: ChannelRequest, + private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, - - id: string, - connection: Channel['connection'], ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -65,8 +67,6 @@ class GlobalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -76,28 +76,3 @@ class GlobalTimelineChannel extends Channel { this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class GlobalTimelineChannelService implements MiChannelService { - public readonly shouldShare = GlobalTimelineChannel.shouldShare; - public readonly requireCredential = GlobalTimelineChannel.requireCredential; - public readonly kind = GlobalTimelineChannel.kind; - - constructor( - private metaService: MetaService, - private roleService: RoleService, - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): GlobalTimelineChannel { - return new GlobalTimelineChannel( - this.metaService, - this.roleService, - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 8105f15cb1..1456b4f262 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -3,28 +3,30 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; 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 type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class HashtagChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class HashtagChannel extends Channel { public readonly chName = 'hashtag'; public static shouldShare = false; public static requireCredential = false as const; private q: string[][]; constructor( - private noteEntityService: NoteEntityService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -53,8 +55,6 @@ class HashtagChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -64,24 +64,3 @@ class HashtagChannel extends Channel { this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class HashtagChannelService implements MiChannelService { - public readonly shouldShare = HashtagChannel.shouldShare; - public readonly requireCredential = HashtagChannel.requireCredential; - public readonly kind = HashtagChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): HashtagChannel { - return new HashtagChannel( - this.noteEntityService, - id, - connection, - ); - } -} 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 66644ed58c..665c11b692 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -3,15 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; 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 type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class HomeTimelineChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class HomeTimelineChannel extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = false; public static requireCredential = true as const; @@ -20,12 +22,12 @@ class HomeTimelineChannel extends Channel { private withFiles: boolean; constructor( - private noteEntityService: NoteEntityService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -44,7 +46,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; @@ -86,8 +91,6 @@ class HomeTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -97,24 +100,3 @@ class HomeTimelineChannel extends Channel { this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class HomeTimelineChannelService implements MiChannelService { - public readonly shouldShare = HomeTimelineChannel.shouldShare; - public readonly requireCredential = HomeTimelineChannel.requireCredential; - public readonly kind = HomeTimelineChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): HomeTimelineChannel { - return new HomeTimelineChannel( - this.noteEntityService, - id, - connection, - ); - } -} 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 5681311493..54250d2a90 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -11,9 +11,11 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class HybridTimelineChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class HybridTimelineChannel extends Channel { public readonly chName = 'hybridTimeline'; public static shouldShare = false; public static requireCredential = true as const; @@ -23,14 +25,14 @@ class HybridTimelineChannel extends Channel { private withFiles: boolean; constructor( + @Inject(REQUEST) + request: ChannelRequest, + private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, - - id: string, - connection: Channel['connection'], ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -53,16 +55,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; @@ -100,8 +111,6 @@ class HybridTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -111,28 +120,3 @@ class HybridTimelineChannel extends Channel { this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class HybridTimelineChannelService implements MiChannelService { - public readonly shouldShare = HybridTimelineChannel.shouldShare; - public readonly requireCredential = HybridTimelineChannel.requireCredential; - public readonly kind = HybridTimelineChannel.kind; - - constructor( - private metaService: MetaService, - private roleService: RoleService, - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): HybridTimelineChannel { - return new HybridTimelineChannel( - this.metaService, - this.roleService, - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 2984e18774..b394e9663f 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -11,25 +11,27 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class LocalTimelineChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class LocalTimelineChannel extends Channel { public readonly chName = 'localTimeline'; - public static shouldShare = false; + public static shouldShare = false as const; public static requireCredential = false as const; private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; constructor( + @Inject(REQUEST) + request: ChannelRequest, + private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, - - id: string, - connection: Channel['connection'], ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -75,8 +77,6 @@ class LocalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -86,28 +86,3 @@ class LocalTimelineChannel extends Channel { this.subscriber.off('notesStream', this.onNote); } } - -@Injectable() -export class LocalTimelineChannelService implements MiChannelService { - public readonly shouldShare = LocalTimelineChannel.shouldShare; - public readonly requireCredential = LocalTimelineChannel.requireCredential; - public readonly kind = LocalTimelineChannel.kind; - - constructor( - private metaService: MetaService, - private roleService: RoleService, - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): LocalTimelineChannel { - return new LocalTimelineChannel( - this.metaService, - this.roleService, - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 863d7f4c4e..2ce53ac288 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -3,26 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class MainChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class MainChannel extends Channel { public readonly chName = 'main'; public static shouldShare = true; public static requireCredential = true as const; public static kind = 'read:account'; constructor( - private noteEntityService: NoteEntityService, + @Inject(REQUEST) + request: ChannelRequest, - id: string, - connection: Channel['connection'], + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); } @bindThis @@ -39,7 +41,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.note.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body.note = note; } break; @@ -52,7 +53,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body = note; } break; @@ -63,24 +63,3 @@ class MainChannel extends Channel { }); } } - -@Injectable() -export class MainChannelService implements MiChannelService { - public readonly shouldShare = MainChannel.shouldShare; - public readonly requireCredential = MainChannel.requireCredential; - public readonly kind = MainChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): MainChannel { - return new MainChannel( - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 91b62255b4..a87863f26c 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -4,21 +4,26 @@ */ import Xev from 'xev'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; const ev = new Xev(); -class QueueStatsChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class QueueStatsChannel extends Channel { public readonly chName = 'queueStats'; public static shouldShare = true; public static requireCredential = false as const; - constructor(id: string, connection: Channel['connection']) { - super(id, connection); + constructor( + @Inject(REQUEST) + request: ChannelRequest, + ) { + super(request); //this.onStats = this.onStats.bind(this); //this.onMessage = this.onMessage.bind(this); } @@ -56,22 +61,3 @@ class QueueStatsChannel extends Channel { ev.removeListener('queueStats', this.onStats); } } - -@Injectable() -export class QueueStatsChannelService implements MiChannelService { - public readonly shouldShare = QueueStatsChannel.shouldShare; - public readonly requireCredential = QueueStatsChannel.requireCredential; - public readonly kind = QueueStatsChannel.kind; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): QueueStatsChannel { - return new QueueStatsChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index 7597a1cfa3..58fc16e98c 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -3,31 +3,32 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import type { MiReversiGame } from '@/models/_.js'; -import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { ReversiService } from '@/core/ReversiService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; import { reversiUpdateKeys } from 'misskey-js'; +import { REQUEST } from '@nestjs/core'; -class ReversiGameChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ReversiGameChannel extends Channel { public readonly chName = 'reversiGame'; public static shouldShare = false; public static requireCredential = false as const; private gameId: MiReversiGame['id'] | null = null; constructor( + @Inject(REQUEST) + request: ChannelRequest, + private reversiService: ReversiService, private reversiGameEntityService: ReversiGameEntityService, - - id: string, - connection: Channel['connection'], ) { - super(id, connection); + super(request); } @bindThis @@ -107,25 +108,3 @@ class ReversiGameChannel extends Channel { } } -@Injectable() -export class ReversiGameChannelService implements MiChannelService { - public readonly shouldShare = ReversiGameChannel.shouldShare; - public readonly requireCredential = ReversiGameChannel.requireCredential; - public readonly kind = ReversiGameChannel.kind; - - constructor( - private reversiService: ReversiService, - private reversiGameEntityService: ReversiGameEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ReversiGameChannel { - return new ReversiGameChannel( - this.reversiService, - this.reversiGameEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/reversi.ts b/packages/backend/src/server/api/stream/channels/reversi.ts index 6e88939724..5eff73eeef 100644 --- a/packages/backend/src/server/api/stream/channels/reversi.ts +++ b/packages/backend/src/server/api/stream/channels/reversi.ts @@ -3,22 +3,24 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class ReversiChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ReversiChannel extends Channel { public readonly chName = 'reversi'; public static shouldShare = true; public static requireCredential = true as const; public static kind = 'read:account'; constructor( - id: string, - connection: Channel['connection'], + @Inject(REQUEST) + request: ChannelRequest, ) { - super(id, connection); + super(request); } @bindThis @@ -32,22 +34,3 @@ class ReversiChannel extends Channel { this.subscriber.off(`reversiStream:${this.user!.id}`, this.send); } } - -@Injectable() -export class ReversiChannelService implements MiChannelService { - public readonly shouldShare = ReversiChannel.shouldShare; - public readonly requireCredential = ReversiChannel.requireCredential; - public readonly kind = ReversiChannel.kind; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ReversiChannel { - return new ReversiChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index fcfa26c38b..99e0b69023 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -3,28 +3,30 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class RoleTimelineChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class RoleTimelineChannel extends Channel { public readonly chName = 'roleTimeline'; public static shouldShare = false; public static requireCredential = false as const; private roleId: string; constructor( + @Inject(REQUEST) + request: ChannelRequest, + private noteEntityService: NoteEntityService, private roleservice: RoleService, - - id: string, - connection: Channel['connection'], ) { - super(id, connection); + super(request); //this.onNote = this.onNote.bind(this); } @@ -60,26 +62,3 @@ class RoleTimelineChannel extends Channel { this.subscriber.off(`roleTimelineStream:${this.roleId}`, this.onEvent); } } - -@Injectable() -export class RoleTimelineChannelService implements MiChannelService { - public readonly shouldShare = RoleTimelineChannel.shouldShare; - public readonly requireCredential = RoleTimelineChannel.requireCredential; - public readonly kind = RoleTimelineChannel.kind; - - constructor( - private noteEntityService: NoteEntityService, - private roleservice: RoleService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): RoleTimelineChannel { - return new RoleTimelineChannel( - this.noteEntityService, - this.roleservice, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index ec5352d12d..aece5435b0 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -4,21 +4,26 @@ */ import Xev from 'xev'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; const ev = new Xev(); -class ServerStatsChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class ServerStatsChannel extends Channel { public readonly chName = 'serverStats'; public static shouldShare = true; public static requireCredential = false as const; - constructor(id: string, connection: Channel['connection']) { - super(id, connection); + constructor( + @Inject(REQUEST) + request: ChannelRequest, + ) { + super(request); //this.onStats = this.onStats.bind(this); //this.onMessage = this.onMessage.bind(this); } @@ -54,22 +59,3 @@ class ServerStatsChannel extends Channel { ev.removeListener('serverStats', this.onStats); } } - -@Injectable() -export class ServerStatsChannelService implements MiChannelService { - public readonly shouldShare = ServerStatsChannel.shouldShare; - public readonly requireCredential = ServerStatsChannel.requireCredential; - public readonly kind = ServerStatsChannel.kind; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): ServerStatsChannel { - return new ServerStatsChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 4f38351e94..2f7345e150 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -11,9 +11,11 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; -import Channel, { type MiChannelService } from '../channel.js'; +import Channel, { type ChannelRequest } from '../channel.js'; +import { REQUEST } from '@nestjs/core'; -class UserListChannel extends Channel { +@Injectable({ scope: Scope.TRANSIENT }) +export class UserListChannel extends Channel { public readonly chName = 'userList'; public static shouldShare = false; public static requireCredential = false as const; @@ -24,14 +26,18 @@ class UserListChannel extends Channel { private withRenotes: boolean; constructor( + @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - private userListMembershipsRepository: UserListMembershipsRepository, - private noteEntityService: NoteEntityService, - id: string, - connection: Channel['connection'], + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + + @Inject(REQUEST) + request: ChannelRequest, + + private noteEntityService: NoteEntityService, ) { - super(id, connection); + super(request); //this.updateListUsers = this.updateListUsers.bind(this); //this.onNote = this.onNote.bind(this); } @@ -118,8 +124,6 @@ class UserListChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } @@ -132,32 +136,3 @@ class UserListChannel extends Channel { clearInterval(this.listUsersClock); } } - -@Injectable() -export class UserListChannelService implements MiChannelService { - public readonly shouldShare = UserListChannel.shouldShare; - public readonly requireCredential = UserListChannel.requireCredential; - public readonly kind = UserListChannel.kind; - - constructor( - @Inject(DI.userListsRepository) - private userListsRepository: UserListsRepository, - - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, - - private noteEntityService: NoteEntityService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): UserListChannel { - return new UserListChannel( - this.userListsRepository, - this.userListMembershipsRepository, - this.noteEntityService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/file/FileServerDriveHandler.ts b/packages/backend/src/server/file/FileServerDriveHandler.ts new file mode 100644 index 0000000000..51b527b146 --- /dev/null +++ b/packages/backend/src/server/file/FileServerDriveHandler.ts @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import rename from 'rename'; +import type { Config } from '@/config.js'; +import type { IImageStreamable } from '@/core/ImageProcessingService.js'; +import { contentDisposition } from '@/misc/content-disposition.js'; +import { correctFilename } from '@/misc/correct-filename.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; +import { VideoProcessingService } from '@/core/VideoProcessingService.js'; +import { attachStreamCleanup, handleRangeRequest, setFileResponseHeaders, getSafeContentType, needsCleanup } from './FileServerUtils.js'; +import type { FileServerFileResolver } from './FileServerFileResolver.js'; +import type { FastifyReply, FastifyRequest } from 'fastify'; + +export class FileServerDriveHandler { + constructor( + private config: Config, + private fileResolver: FileServerFileResolver, + private assetsPath: string, + private videoProcessingService: VideoProcessingService, + ) {} + + public async handle(request: FastifyRequest<{ Params: { key: string } }>, reply: FastifyReply) { + const key = request.params.key; + const file = await this.fileResolver.resolveFileByAccessKey(key); + + if (file.kind === 'not-found') { + reply.code(404); + reply.header('Cache-Control', 'max-age=86400'); + return reply.sendFile('/dummy.png', this.assetsPath); + } + + if (file.kind === 'unavailable') { + reply.code(204); + reply.header('Cache-Control', 'max-age=86400'); + return; + } + + try { + if (file.kind === 'remote') { + let image: IImageStreamable | null = null; + + if (file.fileRole === 'thumbnail') { + if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { + reply.header('Cache-Control', 'max-age=31536000, immutable'); + + const url = new URL(`${this.config.mediaProxy}/static.webp`); + url.searchParams.set('url', file.url); + url.searchParams.set('static', '1'); + + file.cleanup(); + return await reply.redirect(url.toString(), 301); + } else if (file.mime.startsWith('video/')) { + const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url); + if (externalThumbnail) { + file.cleanup(); + return await reply.redirect(externalThumbnail, 301); + } + + image = await this.videoProcessingService.generateVideoThumbnail(file.path); + } + } + + if (file.fileRole === 'webpublic') { + if (['image/svg+xml'].includes(file.mime)) { + reply.header('Cache-Control', 'max-age=31536000, immutable'); + + const url = new URL(`${this.config.mediaProxy}/svg.webp`); + url.searchParams.set('url', file.url); + + file.cleanup(); + return await reply.redirect(url.toString(), 301); + } + } + + image ??= { + data: handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path), + ext: file.ext, + type: file.mime, + }; + + attachStreamCleanup(image.data, file.cleanup); + + reply.header('Content-Type', getSafeContentType(image.type)); + reply.header('Content-Length', file.file.size); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Disposition', + contentDisposition( + 'inline', + correctFilename(file.filename, image.ext), + ), + ); + return image.data; + } + + if (file.fileRole !== 'original') { + const filename = rename(file.filename, { + suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web', + extname: file.ext ? `.${file.ext}` : '.unknown', + }).toString(); + + setFileResponseHeaders(reply, { mime: file.mime, filename }); + return handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path); + } else { + setFileResponseHeaders(reply, { mime: file.file.type, filename: file.filename, size: file.file.size }); + return handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path); + } + } catch (e) { + if (file.kind === 'remote') file.cleanup(); + throw e; + } + } +} diff --git a/packages/backend/src/server/file/FileServerFileResolver.ts b/packages/backend/src/server/file/FileServerFileResolver.ts new file mode 100644 index 0000000000..687d486efd --- /dev/null +++ b/packages/backend/src/server/file/FileServerFileResolver.ts @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js'; +import { createTemp } from '@/misc/create-temp.js'; +import type { DownloadService } from '@/core/DownloadService.js'; +import type { FileInfoService } from '@/core/FileInfoService.js'; +import type { InternalStorageService } from '@/core/InternalStorageService.js'; + +export type DownloadedFileResult = { + kind: 'downloaded'; + mime: string; + ext: string | null; + path: string; + cleanup: () => void; + filename: string; +}; + +export type FileResolveResult = + | { kind: 'not-found' } + | { kind: 'unavailable' } + | { + kind: 'stored'; + fileRole: 'thumbnail' | 'webpublic' | 'original'; + file: MiDriveFile; + filename: string; + mime: string; + ext: string | null; + path: string; + } + | { + kind: 'remote'; + fileRole: 'thumbnail' | 'webpublic' | 'original'; + file: MiDriveFile; + filename: string; + url: string; + mime: string; + ext: string | null; + path: string; + cleanup: () => void; + }; + +export class FileServerFileResolver { + constructor( + private driveFilesRepository: DriveFilesRepository, + private fileInfoService: FileInfoService, + private downloadService: DownloadService, + private internalStorageService: InternalStorageService, + ) {} + + public async downloadAndDetectTypeFromUrl(url: string): Promise { + const [path, cleanup] = await createTemp(); + try { + const { filename } = await this.downloadService.downloadUrl(url, path); + + const { mime, ext } = await this.fileInfoService.detectType(path); + + return { + kind: 'downloaded', + mime, ext, + path, cleanup, + filename, + }; + } catch (e) { + cleanup(); + throw e; + } + } + + public async resolveFileByAccessKey(key: string): Promise { + // Fetch drive file + const file = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.accessKey = :accessKey', { accessKey: key }) + .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) + .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) + .getOne(); + + if (file == null) return { kind: 'not-found' }; + + const isThumbnail = file.thumbnailAccessKey === key; + const isWebpublic = file.webpublicAccessKey === key; + + if (!file.storedInternal) { + if (!(file.isLink && file.uri)) return { kind: 'unavailable' }; + const result = await this.downloadAndDetectTypeFromUrl(file.uri); + const { kind: _kind, ...downloaded } = result; + file.size = (await fs.promises.stat(downloaded.path)).size; // DB file.sizeは正確とは限らないので + return { + kind: 'remote', + ...downloaded, + url: file.uri, + fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original', + file, + filename: file.name, + }; + } + + const path = this.internalStorageService.resolvePath(key); + + if (isThumbnail || isWebpublic) { + const { mime, ext } = await this.fileInfoService.detectType(path); + return { + kind: 'stored', + fileRole: isThumbnail ? 'thumbnail' : 'webpublic', + file, + filename: file.name, + mime, ext, + path, + }; + } + + return { + kind: 'stored', + fileRole: 'original', + file, + filename: file.name, + // 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる + mime: this.fileInfoService.fixMime(file.type), + ext: null, + path, + }; + } +} diff --git a/packages/backend/src/server/file/FileServerProxyHandler.ts b/packages/backend/src/server/file/FileServerProxyHandler.ts new file mode 100644 index 0000000000..41e8e47ba5 --- /dev/null +++ b/packages/backend/src/server/file/FileServerProxyHandler.ts @@ -0,0 +1,272 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import sharp from 'sharp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; +import type { Config } from '@/config.js'; +import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { StatusError } from '@/misc/status-error.js'; +import { contentDisposition } from '@/misc/content-disposition.js'; +import { correctFilename } from '@/misc/correct-filename.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; +import { IImageStreamable, ImageProcessingService, webpDefault } from '@/core/ImageProcessingService.js'; +import { createRangeStream, attachStreamCleanup, needsCleanup } from './FileServerUtils.js'; +import type { DownloadedFileResult, FileResolveResult, FileServerFileResolver } from './FileServerFileResolver.js'; +import type { FastifyReply, FastifyRequest } from 'fastify'; + +type ProxySource = DownloadedFileResult | FileResolveResult; +type CleanupableFile = ProxySource & { cleanup: () => void }; +type AvailableFile = Exclude; +type ProxyQuery = { + emoji?: string; + avatar?: string; + static?: string; + preview?: string; + badge?: string; + origin?: string; + url?: string; +}; + +export class FileServerProxyHandler { + constructor( + private config: Config, + private fileResolver: FileServerFileResolver, + private assetsPath: string, + private imageProcessingService: ImageProcessingService, + ) {} + + public async handle(request: FastifyRequest<{ Params: { url: string }; Querystring: ProxyQuery }>, reply: FastifyReply) { + const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url; + + if (typeof url !== 'string') { + reply.code(400); + return; + } + + // アバタークロップなど、どうしてもオリジンである必要がある場合 + const mustOrigin = 'origin' in request.query; + + if (this.config.externalMediaProxyEnabled && !mustOrigin) { + return await this.redirectToExternalProxy(request, reply); + } + + this.validateUserAgent(request); + + // Create temp file + const file = await this.getStreamAndTypeFromUrl(url); + if (file.kind === 'not-found') { + reply.code(404); + reply.header('Cache-Control', 'max-age=86400'); + return reply.sendFile('/dummy.png', this.assetsPath); + } + + if (file.kind === 'unavailable') { + reply.code(204); + reply.header('Cache-Control', 'max-age=86400'); + return; + } + + try { + const image = await this.processImage(file, request, reply); + + if (needsCleanup(file)) { + attachStreamCleanup(image.data, file.cleanup); + } + + reply.header('Content-Type', image.type); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Disposition', + contentDisposition( + 'inline', + correctFilename(file.filename, image.ext), + ), + ); + return image.data; + } catch (e) { + if (needsCleanup(file)) file.cleanup(); + throw e; + } + } + + /** + * 外部メディアプロキシにリダイレクトする + */ + private async redirectToExternalProxy( + request: FastifyRequest<{ Params: { url: string }; Querystring: ProxyQuery }>, + reply: FastifyReply, + ) { + reply.header('Cache-Control', 'public, max-age=259200'); // 3 days + + const url = new URL(`${this.config.mediaProxy}/${request.params.url || ''}`); + + for (const [key, value] of Object.entries(request.query)) { + url.searchParams.append(key, value); + } + + return reply.redirect(url.toString(), 301); + } + + /** + * User-Agent を検証する + */ + private validateUserAgent(request: FastifyRequest): void { + if (!request.headers['user-agent']) { + throw new StatusError('User-Agent is required', 400, 'User-Agent is required'); + } + if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) { + throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive'); + } + } + + /** + * 画像を処理してストリーム可能な形式に変換する + */ + private async processImage( + file: AvailableFile, + request: FastifyRequest<{ Params: { url: string }; Querystring: ProxyQuery }>, + reply: FastifyReply, + ): Promise { + const query = request.query; + + const requiresImageConversion = 'emoji' in query || 'avatar' in query || 'static' in query || 'preview' in query || 'badge' in query; + const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image-with-bmp'); + if (requiresImageConversion && !isConvertibleImage) { + throw new StatusError('Unexpected mime', 404); + } + + if ('emoji' in query || 'avatar' in query) { + return this.processEmojiOrAvatar(file, query); + } + + if ('static' in query) { + return this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422); + } + + if ('preview' in query) { + return this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200); + } + + if ('badge' in query) { + return this.processBadge(file); + } + + if (file.mime === 'image/svg+xml') { + return this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048); + } + + if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) { + throw new StatusError('Rejected type', 403, 'Rejected type'); + } + + return this.createDefaultStream(file, request, reply); + } + + /** + * 絵文字またはアバター用の画像を処理する + */ + private async processEmojiOrAvatar( + file: AvailableFile, + query: Pick, + ): Promise { + const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp'); + if (!isAnimationConvertibleImage && !('static' in query)) { + return { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } + + const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in query) })) + .resize({ + height: 'emoji' in query ? 128 : 320, + withoutEnlargement: true, + }) + .webp(webpDefault); + + return { + data, + ext: 'webp', + type: 'image/webp', + }; + } + + /** + * バッジ用の画像を処理する + */ + private async processBadge(file: AvailableFile): Promise { + const mask = (await sharpBmp(file.path, file.mime)) + .resize(96, 96, { + fit: 'contain', + position: 'centre', + withoutEnlargement: false, + }) + .greyscale() + .normalise() + .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast + .flatten({ background: '#000' }) + .toColorspace('b-w'); + + const stats = await mask.clone().stats(); + + if (stats.entropy < 0.1) { + throw new StatusError('Skip to provide badge', 404); + } + + const data = sharp({ + create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, + }) + .pipelineColorspace('b-w') + .boolean(await mask.png().toBuffer(), 'eor'); + + return { + data: await data.png().toBuffer(), + ext: 'png', + type: 'image/png', + }; + } + + /** + * デフォルトのストリームを作成する(Range リクエスト対応) + */ + private createDefaultStream( + file: AvailableFile, + request: FastifyRequest, + reply: FastifyReply, + ): IImageStreamable { + if (request.headers.range && 'file' in file && file.file.size > 0) { + const { stream, start, end, chunksize } = createRangeStream(request.headers.range as string, file.file.size, file.path); + + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + + return { + data: stream, + ext: file.ext, + type: file.mime, + }; + } + + return { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } + + private async getStreamAndTypeFromUrl(url: string): Promise { + if (url.startsWith(`${this.config.url}/files/`)) { + const key = url.replace(`${this.config.url}/files/`, '').split('/').shift(); + if (!key) throw new StatusError('Invalid File Key', 400, 'Invalid File Key'); + + return await this.fileResolver.resolveFileByAccessKey(key); + } + + return await this.fileResolver.downloadAndDetectTypeFromUrl(url); + } +} diff --git a/packages/backend/src/server/file/FileServerUtils.ts b/packages/backend/src/server/file/FileServerUtils.ts new file mode 100644 index 0000000000..c5995a2cca --- /dev/null +++ b/packages/backend/src/server/file/FileServerUtils.ts @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { contentDisposition } from '@/misc/content-disposition.js'; +import type { IImageStreamable } from '@/core/ImageProcessingService.js'; +import type { FastifyReply } from 'fastify'; + +export type RangeStream = { + stream: fs.ReadStream; + start: number; + end: number; + chunksize: number; +}; + +/** + * Range リクエストに対応したストリームを作成する + */ +export function createRangeStream(rangeHeader: string, size: number, path: string): RangeStream { + const parts = rangeHeader.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : size - 1; + if (end > size) { + end = size - 1; + } + const chunksize = end - start + 1; + + return { + stream: fs.createReadStream(path, { start, end }), + start, + end, + chunksize, + }; +} + +/** + * ストリームにcleanupハンドラを設定する + * ストリームでない場合は即座にcleanupを実行する + */ +export function attachStreamCleanup(data: IImageStreamable['data'], cleanup: () => void): void { + if ('pipe' in data && typeof data.pipe === 'function') { + data.on('end', cleanup); + data.on('close', cleanup); + } else { + cleanup(); + } +} + +/** + * MIME タイプがブラウザセーフかどうかに応じて Content-Type を返す + */ +export function getSafeContentType(mime: string): string { + return FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'; +} + +/** + * Range リクエストを処理してストリームを返す + * Range ヘッダーがない場合は通常のストリームを返す + */ +export function handleRangeRequest( + reply: FastifyReply, + rangeHeader: string | undefined, + size: number, + path: string, +): fs.ReadStream { + if (rangeHeader && size > 0) { + const { stream, start, end, chunksize } = createRangeStream(rangeHeader, size, path); + reply.header('Content-Range', `bytes ${start}-${end}/${size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + return stream; + } + return fs.createReadStream(path); +} + +export type FileResponseOptions = { + mime: string; + filename: string; + size?: number; + cacheControl?: string; +}; + +/** + * ファイルレスポンス用の共通ヘッダーを設定する + */ +export function setFileResponseHeaders( + reply: FastifyReply, + options: FileResponseOptions, +): void { + reply.header('Content-Type', getSafeContentType(options.mime)); + reply.header('Cache-Control', options.cacheControl ?? 'max-age=31536000, immutable'); + reply.header('Content-Disposition', contentDisposition('inline', options.filename)); + if (options.size !== undefined) { + reply.header('Content-Length', options.size); + } +} + +/** + * cleanup が必要なファイルかどうかを判定する型ガード + */ +export function needsCleanup void }>(file: T): file is T & { cleanup: () => void } { + return 'cleanup' in file && typeof file.cleanup === 'function'; +} diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index cdd7102666..47f4bf947d 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -6,18 +6,15 @@ import dns from 'node:dns/promises'; import { fileURLToPath } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import { JSDOM } from 'jsdom'; +import * as htmlParser from 'node-html-parser'; import httpLinkHeader from 'http-link-header'; import ipaddr from 'ipaddr.js'; import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize'; import oauth2Pkce from 'oauth2orize-pkce'; import fastifyCors from '@fastify/cors'; -import fastifyView from '@fastify/view'; -import pug from 'pug'; import bodyParser from 'body-parser'; import fastifyExpress from '@fastify/express'; import { verifyChallenge } from 'pkce-challenge'; -import { mf2 } from 'microformats-parser'; import { permissions as kinds } from 'misskey-js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; @@ -32,6 +29,8 @@ import { MemoryKVCache } from '@/misc/cache.js'; import { LoggerService } from '@/core/LoggerService.js'; import Logger from '@/logger.js'; import { StatusError } from '@/misc/status-error.js'; +import { HtmlTemplateService } from '@/server/web/HtmlTemplateService.js'; +import { OAuthPage } from '@/server/web/views/oauth.js'; import type { ServerResponse } from 'node:http'; import type { FastifyInstance } from 'fastify'; @@ -98,45 +97,109 @@ interface ClientInformation { logo: string | null; } -// https://indieauth.spec.indieweb.org/#client-information-discovery -// "Authorization servers SHOULD support parsing the [h-app] Microformat from the client_id, -// and if there is an [h-app] with a url property matching the client_id URL, -// then it should use the name and icon and display them on the authorization prompt." -// (But we don't display any icon for now) -// https://indieauth.spec.indieweb.org/#redirect-url -// "The client SHOULD publish one or more tags or Link HTTP headers with a rel attribute -// of redirect_uri at the client_id URL. -// Authorization endpoints verifying that a redirect_uri is allowed for use by a client MUST -// look for an exact match of the given redirect_uri in the request against the list of -// redirect_uris discovered after resolving any relative URLs." +function parseMicroformats(doc: htmlParser.HTMLElement, baseUrl: string, id: string): { name: string | null; logo: string | null; } { + let name: string | null = null; + let logo: string | null = null; + + const hApp = doc.querySelector('.h-app'); + if (hApp == null) return { name, logo }; + + const nameEl = hApp.querySelector('.p-name'); + if (nameEl != null) { + const href = nameEl.attributes.href || nameEl.attributes.src; + if (href != null && new URL(href, baseUrl).toString() === new URL(id).toString()) { + name = nameEl.textContent.trim(); + } + } + + const logoEl = hApp.querySelector('.u-logo'); + if (logoEl != null) { + const href = logoEl.attributes.href || logoEl.attributes.src; + if (href != null) { + logo = new URL(href, baseUrl).toString(); + } + } + + return { name, logo }; +} + async function discoverClientInformation(logger: Logger, httpRequestService: HttpRequestService, id: string): Promise { try { const res = await httpRequestService.send(id); - const redirectUris: string[] = []; + const redirectUris: string[] = []; + let name = id; + let logo: string | null = null; + + // https://indieauth.spec.indieweb.org/#redirect-url + // "The client SHOULD publish one or more tags or Link HTTP headers with a rel attribute + // of redirect_uri at the client_id URL. + // Authorization endpoints verifying that a redirect_uri is allowed for use by a client MUST + // look for an exact match of the given redirect_uri in the request against the list of + // redirect_uris discovered after resolving any relative URLs." const linkHeader = res.headers.get('link'); if (linkHeader) { redirectUris.push(...httpLinkHeader.parse(linkHeader).get('rel', 'redirect_uri').map(r => r.uri)); } - const text = await res.text(); - const fragment = JSDOM.fragment(text); + if (res.headers.get('content-type')?.includes('application/json')) { + // Client discovery via JSON document (11 July 2024 spec) + // https://indieauth.spec.indieweb.org/#client-metadata + // "Clients SHOULD have a JSON [RFC7159] document at their client_id URL containing + // client metadata defined in [RFC7591], the minimum properties for an IndieAuth + // client defined below." - redirectUris.push(...[...fragment.querySelectorAll('link[rel=redirect_uri][href]')].map(el => el.href)); + const json = await res.json() as { + client_id: string; + client_name?: string; + client_uri: string; + logo_uri?: string; + redirect_uris?: string[]; + }; - let name = id; - let logo: string | null = null; - if (text) { - const microformats = mf2(text, { baseUrl: res.url }); - const correspondingProperties = microformats.items.find(item => item.type?.includes('h-app') && item.properties.url.includes(id)); - if (correspondingProperties) { - const nameProperty = correspondingProperties.properties.name?.[0]; - if (typeof nameProperty === 'string') { - name = nameProperty; + // https://indieauth.spec.indieweb.org/#client-metadata-li-1 + // "The authorization server MUST verify that the client_id in the document matches the + // client_id of the URL where the document was retrieved." + if (json.client_id !== id) { + throw new AuthorizationError('client_id in the document does not match the client_id URL', 'invalid_request'); + } + + // https://indieauth.spec.indieweb.org/#client-metadata-li-1 + // "The client_uri MUST be a prefix of the client_id." + if (!json.client_uri || !id.startsWith(json.client_uri)) { + throw new AuthorizationError('client_uri is not a prefix of client_id', 'invalid_request'); + } + + if (typeof json.client_name === 'string') { + name = json.client_name; + } + + if (typeof json.logo_uri === 'string') { + // Since uri can be relative, resolve it against the document URL + logo = new URL(json.logo_uri, res.url).toString(); + } + + if (Array.isArray(json.redirect_uris)) { + redirectUris.push(...json.redirect_uris.filter((uri): uri is string => typeof uri === 'string')); + } + } else { + // Client discovery via HTML microformats (12 February 2022 spec) + // https://indieauth.spec.indieweb.org/20220212/#client-information-discovery + // "Authorization servers SHOULD support parsing the [h-app] Microformat from the client_id, + // and if there is an [h-app] with a url property matching the client_id URL, + // then it should use the name and icon and display them on the authorization prompt." + const text = await res.text(); + const doc = htmlParser.parse(`

${text}
`); + + redirectUris.push(...[...doc.querySelectorAll('link[rel=redirect_uri][href]')].map(el => el.attributes.href)); + + if (text) { + const microformats = parseMicroformats(doc, res.url, id); + if (typeof microformats.name === 'string') { + name = microformats.name; } - const logoProperty = correspondingProperties.properties.logo?.[0]; - if (typeof logoProperty === 'string') { - logo = logoProperty; + if (typeof microformats.logo === 'string') { + logo = microformats.logo; } } } @@ -152,6 +215,8 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt logger.error('Error while fetching client information', { err }); if (err instanceof StatusError) { throw new AuthorizationError('Failed to fetch client information', 'invalid_request'); + } else if (err instanceof AuthorizationError) { + throw err; } else { throw new AuthorizationError('Failed to parse client information', 'server_error'); } @@ -253,6 +318,7 @@ export class OAuth2ProviderService { private usersRepository: UsersRepository, private cacheService: CacheService, loggerService: LoggerService, + private htmlTemplateService: HtmlTemplateService, ) { this.#logger = loggerService.getLogger('oauth'); @@ -386,24 +452,16 @@ export class OAuth2ProviderService { this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`); reply.header('Cache-Control', 'no-store'); - return await reply.view('oauth', { + return await HtmlTemplateService.replyHtml(reply, OAuthPage({ + ...await this.htmlTemplateService.getCommonData(), transactionId: oauth2.transactionID, clientName: oauth2.client.name, - clientLogo: oauth2.client.logo, - scope: oauth2.req.scope.join(' '), - }); + clientLogo: oauth2.client.logo ?? undefined, + scope: oauth2.req.scope, + })); }); fastify.post('/decision', async () => { }); - fastify.register(fastifyView, { - root: fileURLToPath(new URL('../web/views', import.meta.url)), - engine: { pug }, - defaultContext: { - version: this.config.version, - config: this.config, - }, - }); - await fastify.register(fastifyExpress); fastify.use('/authorize', this.#server.authorize(((areq, done) => { (async (): Promise> => { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 30a911088e..24bc619e79 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -4,37 +4,22 @@ */ import { randomUUID } from 'node:crypto'; -import { dirname } from 'node:path'; +import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import sharp from 'sharp'; -import pug from 'pug'; import { In, IsNull } from 'typeorm'; import fastifyStatic from '@fastify/static'; -import fastifyView from '@fastify/view'; import fastifyProxy from '@fastify/http-proxy'; import vary from 'vary'; -import htmlSafeJsonStringify from 'htmlescape'; import type { Config } from '@/config.js'; -import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; -import type { - DbQueue, - DeliverQueue, - EndedPollNotificationQueue, - InboxQueue, - ObjectStorageQueue, - RelationshipQueue, - SystemQueue, - UserWebhookDeliverQueue, - SystemWebhookDeliverQueue, -} from '@/core/QueueModule.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import { MetaEntityService } from '@/core/entities/MetaEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; @@ -53,26 +38,60 @@ import type { } from '@/models/_.js'; import type Logger from '@/logger.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; +import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; -import { RoleService } from '@/core/RoleService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js'; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; -import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; +import { HtmlTemplateService } from './HtmlTemplateService.js'; + +import { BasePage } from './views/base.js'; +import { UserPage } from './views/user.js'; +import { NotePage } from './views/note.js'; +import { PagePage } from './views/page.js'; +import { ClipPage } from './views/clip.js'; +import { FlashPage } from './views/flash.js'; +import { GalleryPostPage } from './views/gallery-post.js'; +import { ChannelPage } from './views/channel.js'; +import { ReversiGamePage } from './views/reversi-game.js'; +import { AnnouncementPage } from './views/announcement.js'; +import { BaseEmbed } from './views/base-embed.js'; +import { InfoCardPage } from './views/info-card.js'; +import { BiosPage } from './views/bios.js'; +import { CliPage } from './views/cli.js'; +import { FlushPage } from './views/flush.js'; +import { ErrorPage } from './views/error.js'; + +import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const staticAssets = `${_dirname}/../../../assets/`; -const clientAssets = `${_dirname}/../../../../frontend/assets/`; -const assets = `${_dirname}/../../../../../built/_frontend_dist_/`; -const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; -const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`; -const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`; -const tarball = `${_dirname}/../../../../../built/tarball/`; +let rootDir = _dirname; +// 見つかるまで上に遡る +while (!fs.existsSync(resolve(rootDir, 'packages'))) { + const parentDir = dirname(rootDir); + if (parentDir === rootDir) { + throw new Error('Cannot find root directory'); + } + rootDir = parentDir; +} + +const backendRootDir = resolve(rootDir, 'packages/backend'); +const frontendRootDir = resolve(rootDir, 'packages/frontend'); + +const staticAssets = resolve(backendRootDir, 'assets'); +const clientAssets = resolve(frontendRootDir, 'assets'); +const assets = resolve(rootDir, 'built/_frontend_dist_'); +const swAssets = resolve(rootDir, 'built/_sw_dist_'); +const fluentEmojisDir = resolve(rootDir, 'fluent-emojis/dist'); +const twemojiDir = resolve(backendRootDir, 'node_modules/@discordapp/twemoji/dist/svg'); +const frontendViteOut = resolve(rootDir, 'built/_frontend_vite_'); +const frontendEmbedViteOut = resolve(rootDir, 'built/_frontend_embed_vite_'); +const tarball = resolve(rootDir, 'built/tarball'); @Injectable() export class ClientServerService { @@ -119,7 +138,6 @@ export class ClientServerService { private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private pageEntityService: PageEntityService, - private metaEntityService: MetaEntityService, private galleryPostEntityService: GalleryPostEntityService, private clipEntityService: ClipEntityService, private channelEntityService: ChannelEntityService, @@ -127,18 +145,8 @@ export class ClientServerService { private announcementEntityService: AnnouncementEntityService, private urlPreviewService: UrlPreviewService, private feedService: FeedService, - private roleService: RoleService, + private htmlTemplateService: HtmlTemplateService, private clientLoggerService: ClientLoggerService, - - @Inject('queue:system') public systemQueue: SystemQueue, - @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, - @Inject('queue:deliver') public deliverQueue: DeliverQueue, - @Inject('queue:inbox') public inboxQueue: InboxQueue, - @Inject('queue:db') public dbQueue: DbQueue, - @Inject('queue:relationship') public relationshipQueue: RelationshipQueue, - @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, - @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, - @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { //this.createServer = this.createServer.bind(this); } @@ -188,6 +196,10 @@ export class ClientServerService { 'url': 'url', }, }, + 'shortcuts': [{ + 'name': 'Safemode', + 'url': '/?safemode=true', + }], }; manifest = { @@ -199,34 +211,9 @@ export class ClientServerService { return (manifest); } - @bindThis - private async generateCommonPugData(meta: MiMeta) { - return { - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - appleTouchIcon: meta.app512IconUrl, - themeColor: meta.themeColor, - serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg', - infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', - notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg', - instanceUrl: this.config.url, - metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)), - now: Date.now(), - }; - } - @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { - fastify.register(fastifyView, { - root: _dirname + '/views', - engine: { - pug: pug, - }, - defaultContext: { - version: this.config.version, - config: this.config, - }, - }); + const configUrl = new URL(this.config.url); fastify.addHook('onRequest', (request, reply, done) => { // クリックジャッキング防止のためiFrameの中に入れられないようにする @@ -236,6 +223,7 @@ export class ClientServerService { //#region vite assets if (this.config.frontendEmbedManifestExists) { + console.log(`[ClientServerService] Using built frontend vite assets. ${frontendViteOut}`); fastify.register((fastify, options, done) => { fastify.register(fastifyStatic, { root: frontendViteOut, @@ -255,7 +243,7 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); + console.log('[ClientServerService] Proxying to Vite dev server.'); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const port = (process.env.VITE_PORT ?? '5173'); @@ -327,7 +315,7 @@ export class ClientServerService { reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - return await reply.sendFile(path, `${_dirname}/../../../../../fluent-emojis/dist/`, { + return reply.sendFile(path, fluentEmojisDir, { maxAge: ms('30 days'), }); }); @@ -342,7 +330,7 @@ export class ClientServerService { reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - return await reply.sendFile(path, `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, { + return reply.sendFile(path, twemojiDir, { maxAge: ms('30 days'), }); }); @@ -356,7 +344,7 @@ export class ClientServerService { } const mask = await sharp( - `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/${path.replace('.png', '')}.svg`, + `${twemojiDir}/${path.replace('.png', '')}.svg`, { density: 1000 }, ) .resize(488, 488) @@ -429,16 +417,15 @@ export class ClientServerService { //#endregion - const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => { + const renderBase = async (reply: FastifyReply, data: Partial[0]> = {}) => { reply.header('Cache-Control', 'public, max-age=30'); - return await reply.view('base', { - img: this.meta.bannerUrl, - url: this.config.url, + return await HtmlTemplateService.replyHtml(reply, BasePage({ + img: this.meta.bannerUrl ?? undefined, title: this.meta.name ?? 'Misskey', - desc: this.meta.description, - ...await this.generateCommonPugData(this.meta), + desc: this.meta.description ?? undefined, + ...await this.htmlTemplateService.getCommonData(), ...data, - }); + })); }; // URL preview endpoint @@ -513,13 +500,13 @@ export class ClientServerService { vary(reply.raw, 'Accept'); - if (user != null) { + if ( + user != null && ( + this.meta.ugcVisibilityForVisitor === 'all' || + (this.meta.ugcVisibilityForVisitor === 'local' && user.host == null) + ) + ) { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const me = profile.fields - ? profile.fields - .filter(filed => filed.value != null && filed.value.match(/^https?:/)) - .map(field => field.value) - : []; reply.header('Cache-Control', 'public, max-age=15'); if (profile.preventAiLearning) { @@ -532,15 +519,15 @@ export class ClientServerService { userProfile: profile, }); - return await reply.view('user', { - user, profile, me, - avatarUrl: _user.avatarUrl, + return await HtmlTemplateService.replyHtml(reply, UserPage({ + user: _user, + profile, sub: request.params.sub, - ...await this.generateCommonPugData(this.meta), - clientCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + clientCtxJson: htmlSafeJsonStringify({ user: _user, }), - }); + })); } else { // リモートユーザーなので // モデレータがAPI経由で参照可能にするために404にはしない @@ -574,10 +561,16 @@ export class ClientServerService { id: request.params.note, visibility: In(['public', 'home']), }, - relations: ['user'], + relations: ['user', 'reply', 'renote'], }); - if (note && !note.user!.requireSigninToViewContents) { + if ( + note && + !note.user!.requireSigninToViewContents && + (this.meta.ugcVisibilityForVisitor === 'all' || + (this.meta.ugcVisibilityForVisitor === 'local' && note.userHost == null) + ) + ) { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); reply.header('Cache-Control', 'public, max-age=15'); @@ -585,17 +578,14 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } - return await reply.view('note', { + return await HtmlTemplateService.replyHtml(reply, NotePage({ note: _note, profile, - avatarUrl: _note.user.avatarUrl, - // TODO: Let locale changeable by instance setting - summary: getNoteSummary(_note), - ...await this.generateCommonPugData(this.meta), - clientCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + clientCtxJson: htmlSafeJsonStringify({ note: _note, }), - }); + })); } else { return await renderBase(reply); } @@ -628,12 +618,11 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } - return await reply.view('page', { + return await HtmlTemplateService.replyHtml(reply, PagePage({ page: _page, profile, - avatarUrl: _page.user.avatarUrl, - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -653,12 +642,11 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } - return await reply.view('flash', { + return await HtmlTemplateService.replyHtml(reply, FlashPage({ flash: _flash, profile, - avatarUrl: _flash.user.avatarUrl, - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -678,15 +666,14 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } - return await reply.view('clip', { + return await HtmlTemplateService.replyHtml(reply, ClipPage({ clip: _clip, profile, - avatarUrl: _clip.user.avatarUrl, - ...await this.generateCommonPugData(this.meta), - clientCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + clientCtxJson: htmlSafeJsonStringify({ clip: _clip, }), - }); + })); } else { return await renderBase(reply); } @@ -704,12 +691,11 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } - return await reply.view('gallery-post', { - post: _post, + return await HtmlTemplateService.replyHtml(reply, GalleryPostPage({ + galleryPost: _post, profile, - avatarUrl: _post.user.avatarUrl, - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -724,10 +710,10 @@ export class ClientServerService { if (channel) { const _channel = await this.channelEntityService.pack(channel); reply.header('Cache-Control', 'public, max-age=15'); - return await reply.view('channel', { + return await HtmlTemplateService.replyHtml(reply, ChannelPage({ channel: _channel, - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -742,10 +728,10 @@ export class ClientServerService { if (game) { const _game = await this.reversiGameEntityService.packDetail(game); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('reversi-game', { - game: _game, - ...await this.generateCommonPugData(this.meta), - }); + return await HtmlTemplateService.replyHtml(reply, ReversiGamePage({ + reversiGame: _game, + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -761,10 +747,10 @@ export class ClientServerService { if (announcement) { const _announcement = await this.announcementEntityService.pack(announcement); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('announcement', { + return await HtmlTemplateService.replyHtml(reply, AnnouncementPage({ announcement: _announcement, - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); } else { return await renderBase(reply); } @@ -797,20 +783,23 @@ export class ClientServerService { const _user = await this.userEntityService.pack(user); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('base-embed', { + return await HtmlTemplateService.replyHtml(reply, BaseEmbed({ title: this.meta.name ?? 'Misskey', - ...await this.generateCommonPugData(this.meta), - embedCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + embedCtxJson: htmlSafeJsonStringify({ user: _user, }), - }); + })); }); fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => { reply.removeHeader('X-Frame-Options'); - const note = await this.notesRepository.findOneBy({ - id: request.params.note, + const note = await this.notesRepository.findOne({ + where: { + id: request.params.note, + }, + relations: ['user', 'reply', 'renote'], }); if (note == null) return; @@ -820,13 +809,13 @@ export class ClientServerService { const _note = await this.noteEntityService.pack(note, null, { detail: true }); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('base-embed', { + return await HtmlTemplateService.replyHtml(reply, BaseEmbed({ title: this.meta.name ?? 'Misskey', - ...await this.generateCommonPugData(this.meta), - embedCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + embedCtxJson: htmlSafeJsonStringify({ note: _note, }), - }); + })); }); fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => { @@ -841,55 +830,66 @@ export class ClientServerService { const _clip = await this.clipEntityService.pack(clip); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('base-embed', { + return await HtmlTemplateService.replyHtml(reply, BaseEmbed({ title: this.meta.name ?? 'Misskey', - ...await this.generateCommonPugData(this.meta), - embedCtx: htmlSafeJsonStringify({ + ...await this.htmlTemplateService.getCommonData(), + embedCtxJson: htmlSafeJsonStringify({ clip: _clip, }), - }); + })); }); fastify.get('/embed/*', async (request, reply) => { reply.removeHeader('X-Frame-Options'); reply.header('Cache-Control', 'public, max-age=3600'); - return await reply.view('base-embed', { + return await HtmlTemplateService.replyHtml(reply, BaseEmbed({ title: this.meta.name ?? 'Misskey', - ...await this.generateCommonPugData(this.meta), - }); + ...await this.htmlTemplateService.getCommonData(), + })); }); fastify.get('/_info_card_', async (request, reply) => { reply.removeHeader('X-Frame-Options'); - return await reply.view('info-card', { + return await HtmlTemplateService.replyHtml(reply, InfoCardPage({ version: this.config.version, - host: this.config.host, + config: this.config, meta: this.meta, - originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }), - originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }), - }); + })); }); //#endregion fastify.get('/bios', async (request, reply) => { - return await reply.view('bios', { + return await HtmlTemplateService.replyHtml(reply, BiosPage({ version: this.config.version, - }); + })); }); fastify.get('/cli', async (request, reply) => { - return await reply.view('cli', { + return await HtmlTemplateService.replyHtml(reply, CliPage({ version: this.config.version, - }); + })); }); - const override = (source: string, target: string, depth = 0) => - [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); - fastify.get('/flush', async (request, reply) => { - return await reply.view('flush'); + let sendHeader = true; + + if (request.headers['origin']) { + const originURL = new URL(request.headers['origin']); + if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https + sendHeader = false; + } + if (originURL.host !== configUrl.host) { + sendHeader = false; + } + } + + if (sendHeader) { + reply.header('Clear-Site-Data', '"*"'); + } + reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60'); + return await HtmlTemplateService.replyHtml(reply, FlushPage()); }); // streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる @@ -903,7 +903,7 @@ export class ClientServerService { return await renderBase(reply); }); - fastify.setErrorHandler(async (error, request, reply) => { + fastify.setErrorHandler(async (error, request, reply) => { const errId = randomUUID(); this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, { path: request.routeOptions.url, @@ -915,10 +915,10 @@ export class ClientServerService { }); reply.code(500); reply.header('Cache-Control', 'max-age=10, must-revalidate'); - return await reply.view('error', { + return await HtmlTemplateService.replyHtml(reply, ErrorPage({ code: error.code, id: errId, - }); + })); }); done(); diff --git a/packages/backend/src/server/web/HtmlTemplateService.ts b/packages/backend/src/server/web/HtmlTemplateService.ts new file mode 100644 index 0000000000..8ff985530d --- /dev/null +++ b/packages/backend/src/server/web/HtmlTemplateService.ts @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promises as fsp } from 'node:fs'; +import { languages } from 'i18n/const'; +import { Injectable, Inject } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; +import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js'; +import { MetaEntityService } from '@/core/entities/MetaEntityService.js'; +import type { FastifyReply } from 'fastify'; +import type { Config } from '@/config.js'; +import type { MiMeta } from '@/models/Meta.js'; +import type { CommonData } from './views/_.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const frontendVitePublic = `${_dirname}/../../../../frontend/public/`; +const frontendEmbedVitePublic = `${_dirname}/../../../../frontend-embed/public/`; + +@Injectable() +export class HtmlTemplateService { + private frontendBootloadersFetched = false; + public frontendBootloaderJs: string | null = null; + public frontendBootloaderCss: string | null = null; + public frontendEmbedBootloaderJs: string | null = null; + public frontendEmbedBootloaderCss: string | null = null; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.meta) + private meta: MiMeta, + + private metaEntityService: MetaEntityService, + ) { + } + + @bindThis + private async prepareFrontendBootloaders() { + if (this.frontendBootloadersFetched) return; + this.frontendBootloadersFetched = true; + + const [bootJs, bootCss, embedBootJs, embedBootCss] = await Promise.all([ + fsp.readFile(`${frontendVitePublic}loader/boot.js`, 'utf-8').catch(() => null), + fsp.readFile(`${frontendVitePublic}loader/style.css`, 'utf-8').catch(() => null), + fsp.readFile(`${frontendEmbedVitePublic}loader/boot.js`, 'utf-8').catch(() => null), + fsp.readFile(`${frontendEmbedVitePublic}loader/style.css`, 'utf-8').catch(() => null), + ]); + + if (bootJs != null) { + this.frontendBootloaderJs = bootJs; + } + + if (bootCss != null) { + this.frontendBootloaderCss = bootCss; + } + + if (embedBootJs != null) { + this.frontendEmbedBootloaderJs = embedBootJs; + } + + if (embedBootCss != null) { + this.frontendEmbedBootloaderCss = embedBootCss; + } + } + + @bindThis + public async getCommonData(): Promise { + await this.prepareFrontendBootloaders(); + + return { + version: this.config.version, + config: this.config, + langs: [...languages], + instanceName: this.meta.name ?? 'Misskey', + icon: this.meta.iconUrl, + appleTouchIcon: this.meta.app512IconUrl, + themeColor: this.meta.themeColor, + serverErrorImageUrl: this.meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg', + infoImageUrl: this.meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', + notFoundImageUrl: this.meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg', + instanceUrl: this.config.url, + metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(this.meta)), + now: Date.now(), + federationEnabled: this.meta.federation !== 'none', + frontendBootloaderJs: this.frontendBootloaderJs, + frontendBootloaderCss: this.frontendBootloaderCss, + frontendEmbedBootloaderJs: this.frontendEmbedBootloaderJs, + frontendEmbedBootloaderCss: this.frontendEmbedBootloaderCss, + }; + } + + public static async replyHtml(reply: FastifyReply, html: string | Promise) { + reply.header('Content-Type', 'text/html; charset=utf-8'); + const _html = await html; + return reply.send(_html); + } +} diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 9b5f0acd2c..bd1dbb430c 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -4,8 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { summaly } from '@misskey-dev/summaly'; -import { SummalyResult } from '@misskey-dev/summaly/built/summary.js'; +import type { SummalyResult } from '@misskey-dev/summaly/built/summary.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; @@ -37,12 +36,10 @@ export class UrlPreviewService { @bindThis private wrap(url?: string | null): string | null { return url != null - ? url.match(/^https?:\/\//) - ? `${this.config.mediaProxy}/preview.webp?${query({ - url, - preview: '1', - })}` - : url + ? `${this.config.mediaProxy}/preview.webp?${query({ + url, + preview: '1', + })}` : null; } @@ -96,8 +93,8 @@ export class UrlPreviewService { summary.icon = this.wrap(summary.icon); summary.thumbnail = this.wrap(summary.thumbnail); - // Cache 7days - reply.header('Cache-Control', 'max-age=604800, immutable'); + // Cache 1day + reply.header('Cache-Control', 'max-age=86400, immutable'); return summary; } catch (err) { @@ -115,7 +112,7 @@ export class UrlPreviewService { } } - private fetchSummary(url: string, meta: MiMeta, lang?: string): Promise { + private async fetchSummary(url: string, meta: MiMeta, lang?: string): Promise { const agent = this.config.proxy ? { http: this.httpRequestService.httpAgent, @@ -123,8 +120,10 @@ export class UrlPreviewService { } : undefined; + const { summaly } = await import('@misskey-dev/summaly'); + return summaly(url, { - followRedirects: false, + followRedirects: this.meta.urlPreviewAllowRedirect, lang: lang ?? 'ja-JP', agent: agent, userAgent: meta.urlPreviewUserAgent ?? undefined, @@ -139,6 +138,7 @@ export class UrlPreviewService { const queryStr = query({ url: url, lang: lang ?? 'ja-JP', + followRedirects: this.meta.urlPreviewAllowRedirect, userAgent: meta.urlPreviewUserAgent ?? undefined, operationTimeout: meta.urlPreviewTimeout, contentLengthLimit: meta.urlPreviewMaximumContentLength, diff --git a/packages/backend/src/server/web/manifest.json b/packages/backend/src/server/web/manifest.json index 41171d62a1..90d4530857 100644 --- a/packages/backend/src/server/web/manifest.json +++ b/packages/backend/src/server/web/manifest.json @@ -34,5 +34,11 @@ "text": "text", "url": "url" } - } + }, + "shortcuts": [ + { + "name": "Safemode", + "url": "/?safemode=true" + } + ] } diff --git a/packages/backend/src/server/web/views/_.ts b/packages/backend/src/server/web/views/_.ts new file mode 100644 index 0000000000..ac7418f362 --- /dev/null +++ b/packages/backend/src/server/web/views/_.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Config } from '@/config.js'; + +export const comment = ``; + +export const defaultDescription = '✨🌎✨ A interplanetary communication platform ✨🚀✨'; + +export type MinimumCommonData = { + version: string; + config: Config; +}; + +export type CommonData = MinimumCommonData & { + langs: string[]; + instanceName: string; + icon: string | null; + appleTouchIcon: string | null; + themeColor: string | null; + serverErrorImageUrl: string; + infoImageUrl: string; + notFoundImageUrl: string; + instanceUrl: string; + now: number; + federationEnabled: boolean; + frontendBootloaderJs: string | null; + frontendBootloaderCss: string | null; + frontendEmbedBootloaderJs: string | null; + frontendEmbedBootloaderCss: string | null; + metaJson?: string; + clientCtxJson?: string; +}; + +export type CommonPropsMinimum> = MinimumCommonData & T; + +export type CommonProps> = CommonData & T; diff --git a/packages/backend/src/server/web/views/_splash.tsx b/packages/backend/src/server/web/views/_splash.tsx new file mode 100644 index 0000000000..ea79b8d61d --- /dev/null +++ b/packages/backend/src/server/web/views/_splash.tsx @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function Splash(props: { + icon?: string | null; +}) { + return ( +
+ +
+ + + + + + + + + + +
+
+ ); +} diff --git a/packages/backend/src/server/web/views/announcement.pug b/packages/backend/src/server/web/views/announcement.pug deleted file mode 100644 index 7a4052e8a4..0000000000 --- a/packages/backend/src/server/web/views/announcement.pug +++ /dev/null @@ -1,21 +0,0 @@ -extends ./base - -block vars - - const title = announcement.title; - - const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text; - - const url = `${config.url}/announcements/${announcement.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content=description) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= description) - meta(property='og:url' content= url) - if announcement.imageUrl - meta(property='og:image' content=announcement.imageUrl) - meta(property='twitter:card' content='summary_large_image') diff --git a/packages/backend/src/server/web/views/announcement.tsx b/packages/backend/src/server/web/views/announcement.tsx new file mode 100644 index 0000000000..bc1c808177 --- /dev/null +++ b/packages/backend/src/server/web/views/announcement.tsx @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function AnnouncementPage(props: CommonProps<{ + announcement: Packed<'Announcement'>; +}>) { + const description = props.announcement.text.length > 100 ? props.announcement.text.slice(0, 100) + '…' : props.announcement.text; + + function ogBlock() { + return ( + <> + + + + + {props.announcement.imageUrl ? ( + <> + + + + ) : null} + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug deleted file mode 100644 index baa0909676..0000000000 --- a/packages/backend/src/server/web/views/base-embed.pug +++ /dev/null @@ -1,72 +0,0 @@ -block vars - -block loadClientEntry - - const entry = config.frontendEmbedEntry; - -doctype html - -html(class='embed') - - head - meta(charset='utf-8') - meta(name='application-name' content='Misskey') - meta(name='referrer' content='origin') - meta(name='theme-color' content= themeColor || '#86b300') - meta(name='theme-color-orig' content= themeColor || '#86b300') - meta(property='og:site_name' content= instanceName || 'Misskey') - meta(property='instance_url' content= instanceUrl) - meta(name='viewport' content='width=device-width, initial-scale=1') - meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') - link(rel='icon' href= icon || '/favicon.ico') - link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') - link(rel='modulepreload' href=`/embed_vite/${entry.file}`) - - if !config.frontendEmbedManifestExists - script(type="module" src="/embed_vite/@vite/client") - - if Array.isArray(entry.css) - each href in entry.css - link(rel='stylesheet' href=`/embed_vite/${href}`) - - title - block title - = title || 'Misskey' - - block meta - meta(name='robots' content='noindex') - - style - include ../style.embed.css - - script. - var VERSION = "#{version}"; - var CLIENT_ENTRY = "#{entry.file}"; - - script(type='application/json' id='misskey_meta' data-generated-at=now) - != metaJson - - script(type='application/json' id='misskey_embedCtx' data-generated-at=now) - != embedCtx - - script - include ../boot.embed.js - - body - noscript: p - | JavaScriptを有効にしてください - br - | Please turn on your JavaScript - div#splash - img#splashIcon(src= icon || '/static-assets/splash.png') - div#splashSpinner - - - - - - - - - - - block content diff --git a/packages/backend/src/server/web/views/base-embed.tsx b/packages/backend/src/server/web/views/base-embed.tsx new file mode 100644 index 0000000000..011b66592e --- /dev/null +++ b/packages/backend/src/server/web/views/base-embed.tsx @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { comment } from '@/server/web/views/_.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Splash } from '@/server/web/views/_splash.js'; +import type { PropsWithChildren, Children } from '@kitajs/html'; + +export function BaseEmbed(props: PropsWithChildren>) { + const now = Date.now(); + + // 変数名をsafeで始めることでエラーをスキップ + const safeMetaJson = props.metaJson; + const safeEmbedCtxJson = props.embedCtxJson; + + return ( + <> + {''} + {comment} + + + + + + + + + + + + + + + {!props.config.frontendEmbedManifestExists ? : null} + + {props.config.frontendEmbedEntry.css != null ? props.config.frontendEmbedEntry.css.map((href) => ( + + )) : null} + + {props.titleSlot ?? {props.title || 'Misskey'}} + + {props.metaSlot} + + + + {props.frontendEmbedBootloaderCss != null ? : } + + + + {safeMetaJson != null ? : null} + {safeEmbedCtxJson != null ? : null} + + {props.frontendEmbedBootloaderJs != null ? : } + + + + + {props.children} + + + + ); +} + diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug deleted file mode 100644 index 3883b5e5ab..0000000000 --- a/packages/backend/src/server/web/views/base.pug +++ /dev/null @@ -1,101 +0,0 @@ -block vars - -block loadClientEntry - - const entry = config.frontendEntry; - - const baseUrl = config.url; - -doctype html - -// - - - _____ _ _ - | |_|___ ___| |_ ___ _ _ - | | | | |_ -|_ -| '_| -_| | | - |_|_|_|_|___|___|_,_|___|_ | - |___| - Thank you for using Misskey! - If you are reading this message... how about joining the development? - https://github.com/misskey-dev/misskey - - -html - - head - meta(charset='utf-8') - meta(name='application-name' content='Misskey') - meta(name='referrer' content='origin') - meta(name='theme-color' content= themeColor || '#86b300') - meta(name='theme-color-orig' content= themeColor || '#86b300') - meta(property='og:site_name' content= instanceName || 'Misskey') - meta(property='instance_url' content= instanceUrl) - meta(name='viewport' content='width=device-width, initial-scale=1') - meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') - link(rel='icon' href= icon || '/favicon.ico') - link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') - link(rel='manifest' href='/manifest.json') - link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`) - link(rel='prefetch' href=serverErrorImageUrl) - link(rel='prefetch' href=infoImageUrl) - link(rel='prefetch' href=notFoundImageUrl) - link(rel='modulepreload' href=`/vite/${entry.file}`) - - if !config.frontendManifestExists - script(type="module" src="/vite/@vite/client") - - if Array.isArray(entry.css) - each href in entry.css - link(rel='stylesheet' href=`/vite/${href}`) - - title - block title - = title || 'Misskey' - - if noindex - meta(name='robots' content='noindex') - - block desc - meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') - - block meta - - block og - meta(property='og:title' content= title || 'Misskey') - meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') - meta(property='og:image' content= img) - meta(property='twitter:card' content='summary') - - style - include ../style.css - - script. - var VERSION = "#{version}"; - var CLIENT_ENTRY = "#{entry.file}"; - - script(type='application/json' id='misskey_meta' data-generated-at=now) - != metaJson - - script(type='application/json' id='misskey_clientCtx' data-generated-at=now) - != clientCtx - - script - include ../boot.js - - body - noscript: p - | JavaScriptを有効にしてください - br - | Please turn on your JavaScript - div#splash - img#splashIcon(src= icon || '/static-assets/splash.png') - div#splashSpinner - - - - - - - - - - - block content diff --git a/packages/backend/src/server/web/views/base.tsx b/packages/backend/src/server/web/views/base.tsx new file mode 100644 index 0000000000..6fa3395fb8 --- /dev/null +++ b/packages/backend/src/server/web/views/base.tsx @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { comment, defaultDescription } from '@/server/web/views/_.js'; +import { Splash } from '@/server/web/views/_splash.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import type { PropsWithChildren, Children } from '@kitajs/html'; + +export function Layout(props: PropsWithChildren>) { + const now = Date.now(); + + // 変数名をsafeで始めることでエラーをスキップ + const safeMetaJson = props.metaJson; + const safeClientCtxJson = props.clientCtxJson; + + return ( + <> + {''} + {comment} + + + + + + + + + + + + + + + + {props.serverErrorImageUrl != null ? : null} + {props.infoImageUrl != null ? : null} + {props.notFoundImageUrl != null ? : null} + + {!props.config.frontendManifestExists ? : null} + + {props.config.frontendEntry.css != null ? props.config.frontendEntry.css.map((href) => ( + + )) : null} + + {props.titleSlot ?? {props.title || 'Misskey'}} + + {props.noindex ? : null} + + {props.descSlot ?? (props.desc != null ? : null)} + + {props.metaSlot} + + {props.ogSlot ?? ( + <> + + + {props.img != null ? : null} + + + )} + + {props.frontendBootloaderCss != null ? : } + + + + {safeMetaJson != null ? : null} + {safeClientCtxJson != null ? : null} + + {props.frontendBootloaderJs != null ? : } + + + + + {props.children} + + + + ); +} + +export { Layout as BasePage }; + diff --git a/packages/backend/src/server/web/views/bios.pug b/packages/backend/src/server/web/views/bios.pug deleted file mode 100644 index 39a151a29b..0000000000 --- a/packages/backend/src/server/web/views/bios.pug +++ /dev/null @@ -1,20 +0,0 @@ -doctype html - -html - - head - meta(charset='utf-8') - meta(name='application-name' content='Misskey') - title Misskey Repair Tool - style - include ../bios.css - script - include ../bios.js - - body - header - h1 Misskey Repair Tool #{version} - main - div.tabs - button#ls edit local storage - div#content diff --git a/packages/backend/src/server/web/views/bios.tsx b/packages/backend/src/server/web/views/bios.tsx new file mode 100644 index 0000000000..9010de8d75 --- /dev/null +++ b/packages/backend/src/server/web/views/bios.tsx @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function BiosPage(props: { + version: string; +}) { + return ( + <> + {''} + + + + + Misskey Repair Tool + + + + +
+

Misskey Repair Tool {props.version}

+
+
+
+ +
+
+
+ + + + + ); +} diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug deleted file mode 100644 index c514025e0b..0000000000 --- a/packages/backend/src/server/web/views/channel.pug +++ /dev/null @@ -1,19 +0,0 @@ -extends ./base - -block vars - - const title = channel.name; - - const url = `${config.url}/channels/${channel.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= channel.description) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= channel.description) - meta(property='og:url' content= url) - meta(property='og:image' content= channel.bannerUrl) - meta(property='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/channel.tsx b/packages/backend/src/server/web/views/channel.tsx new file mode 100644 index 0000000000..7d8123ea85 --- /dev/null +++ b/packages/backend/src/server/web/views/channel.tsx @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function ChannelPage(props: CommonProps<{ + channel: Packed<'Channel'>; +}>) { + + function ogBlock() { + return ( + <> + + + {props.channel.description != null ? : null} + + {props.channel.bannerUrl ? ( + <> + + + + ) : null} + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/cli.pug b/packages/backend/src/server/web/views/cli.pug deleted file mode 100644 index d2cf7c4335..0000000000 --- a/packages/backend/src/server/web/views/cli.pug +++ /dev/null @@ -1,21 +0,0 @@ -doctype html - -html - - head - meta(charset='utf-8') - meta(name='application-name' content='Misskey') - title Misskey Cli - style - include ../cli.css - script - include ../cli.js - - body - header - h1 Misskey Cli #{version} - main - div#form - textarea#text - button#submit submit - div#tl diff --git a/packages/backend/src/server/web/views/cli.tsx b/packages/backend/src/server/web/views/cli.tsx new file mode 100644 index 0000000000..009d982b35 --- /dev/null +++ b/packages/backend/src/server/web/views/cli.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function CliPage(props: { + version: string; +}) { + return ( + <> + {''} + + + + + Misskey CLI Tool + + + + + +
+

Misskey CLI {props.version}

+
+
+
+ + +
+
+
+ + + + + ); +} diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug deleted file mode 100644 index 5a0018803a..0000000000 --- a/packages/backend/src/server/web/views/clip.pug +++ /dev/null @@ -1,35 +0,0 @@ -extends ./base - -block vars - - const user = clip.user; - - const title = clip.name; - - const url = `${config.url}/clips/${clip.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= clip.description) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= clip.description) - meta(property='og:url' content= url) - meta(property='og:image' content= avatarUrl) - meta(property='twitter:card' content='summary') - -block meta - if profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - meta(name='misskey:clip-id' content=clip.id) - - // todo - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/clip.tsx b/packages/backend/src/server/web/views/clip.tsx new file mode 100644 index 0000000000..c3cc505e35 --- /dev/null +++ b/packages/backend/src/server/web/views/clip.tsx @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function ClipPage(props: CommonProps<{ + clip: Packed<'Clip'>; + profile: MiUserProfile; +}>) { + function ogBlock() { + return ( + <> + + + {props.clip.description != null ? : null} + + {props.clip.user.avatarUrl ? ( + <> + + + + ) : null} + + ); + } + + function metaBlock() { + return ( + <> + {props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/error.pug b/packages/backend/src/server/web/views/error.pug deleted file mode 100644 index 6a78d1878c..0000000000 --- a/packages/backend/src/server/web/views/error.pug +++ /dev/null @@ -1,71 +0,0 @@ -doctype html - -// - - - _____ _ _ - | |_|___ ___| |_ ___ _ _ - | | | | |_ -|_ -| '_| -_| | | - |_|_|_|_|___|___|_,_|___|_ | - |___| - Thank you for using Misskey! - If you are reading this message... how about joining the development? - https://github.com/misskey-dev/misskey - - -html - - head - meta(charset='utf-8') - meta(name='viewport' content='width=device-width, initial-scale=1') - meta(name='application-name' content='Misskey') - meta(name='referrer' content='origin') - - title - block title - = 'An error has occurred... | Misskey' - - style - include ../error.css - - script - include ../error.js - -body - svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round") - path(stroke="none", d="M0 0h24v24H0z", fill="none") - path(d="M12 9v2m0 4v.01") - path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75") - - h1(data-i18n="title") Failed to initialize Misskey - - button.button-big(onclick="location.reload();") - span.button-label-big(data-i18n-reload) Reload - - p(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID. - - div#errors - code. - ERROR CODE: #{code} - ERROR ID: #{id} - - p - b(data-i18n="solution") The following actions may solve the problem. - - p(data-i18n="solution1") Update your os and browser - p(data-i18n="solution2") Disable an adblocker - p(data-i18n="solution3") Clear your browser cache - p(data-i18n="solution4") (Tor Browser) Set dom.webaudio.enabled to true - - details(style="color: #86b300;") - summary(data-i18n="otherOption") Other options - a(href="/flush") - button.button-small - span.button-label-small(data-i18n="otherOption1") Clear preferences and cache - br - a(href="/cli") - button.button-small - span.button-label-small(data-i18n="otherOption2") Start the simple client - br - a(href="/bios") - button.button-small - span.button-label-small(data-i18n="otherOption3") Start the repair tool diff --git a/packages/backend/src/server/web/views/error.tsx b/packages/backend/src/server/web/views/error.tsx new file mode 100644 index 0000000000..9d0e60aa30 --- /dev/null +++ b/packages/backend/src/server/web/views/error.tsx @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { comment } from '@/server/web/views/_.js'; +import type { CommonPropsMinimum } from '@/server/web/views/_.js'; + +export function ErrorPage(props: { + title?: string; + code: string; + id: string; +}) { + return ( + <> + {''} + {comment} + + + + + + + {props.title ?? 'An error has occurred... | Misskey'} + + + + + + + + + +

Failed to initialize Misskey

+ + + +

+ If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID. +

+ +
+ + ERROR CODE: {props.code}
+ ERROR ID: {props.id} +
+
+ +

The following actions may solve the problem.

+ +

Update your os and browser

+

Disable an adblocker

+

Clear your browser cache

+

(Tor Browser) Set dom.webaudio.enabled to true

+ +
+ Other options + + + + + + + + + +
+ + + + ); +} diff --git a/packages/backend/src/server/web/views/flash.pug b/packages/backend/src/server/web/views/flash.pug deleted file mode 100644 index 1549aa7906..0000000000 --- a/packages/backend/src/server/web/views/flash.pug +++ /dev/null @@ -1,35 +0,0 @@ -extends ./base - -block vars - - const user = flash.user; - - const title = flash.title; - - const url = `${config.url}/play/${flash.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= flash.summary) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= flash.summary) - meta(property='og:url' content= url) - meta(property='og:image' content= avatarUrl) - meta(property='twitter:card' content='summary') - -block meta - if profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - meta(name='misskey:flash-id' content=flash.id) - - // todo - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/flash.tsx b/packages/backend/src/server/web/views/flash.tsx new file mode 100644 index 0000000000..25a6b2c0ae --- /dev/null +++ b/packages/backend/src/server/web/views/flash.tsx @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function FlashPage(props: CommonProps<{ + flash: Packed<'Flash'>; + profile: MiUserProfile; +}>) { + function ogBlock() { + return ( + <> + + + + + {props.flash.user.avatarUrl ? ( + <> + + + + ) : null} + + ); + } + + function metaBlock() { + return ( + <> + {props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/flush.pug b/packages/backend/src/server/web/views/flush.pug deleted file mode 100644 index a73a45212f..0000000000 --- a/packages/backend/src/server/web/views/flush.pug +++ /dev/null @@ -1,47 +0,0 @@ -doctype html - -html - #msg - script. - const msg = document.getElementById('msg'); - const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`; - - message('Start flushing.'); - - (async function() { - try { - localStorage.clear(); - message('localStorage cleared.'); - - const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { - const delidb = indexedDB.deleteDatabase(name); - delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); - delidb.onerror = e => rej(e) - })); - - await Promise.all(idbPromises); - - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage('clear'); - await navigator.serviceWorker.getRegistrations() - .then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }) - .catch(e => { throw new Error(e) }); - } - - message(successText); - } catch (e) { - message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); - message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) - - console.error(e); - setTimeout(() => { - location = '/'; - }, 10000) - } - })(); - - function message(text) { - msg.insertAdjacentHTML('beforeend', `

[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}

`) - } diff --git a/packages/backend/src/server/web/views/flush.tsx b/packages/backend/src/server/web/views/flush.tsx new file mode 100644 index 0000000000..f3fdc8fcb0 --- /dev/null +++ b/packages/backend/src/server/web/views/flush.tsx @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function FlushPage(props?: {}) { + return ( + <> + {''} + + + + + Clear preferences and cache + + +
+ + + + + ); +} diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug deleted file mode 100644 index 9ae25d9ac8..0000000000 --- a/packages/backend/src/server/web/views/gallery-post.pug +++ /dev/null @@ -1,41 +0,0 @@ -extends ./base - -block vars - - const user = post.user; - - const title = post.title; - - const url = `${config.url}/gallery/${post.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= post.description) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= post.description) - meta(property='og:url' content= url) - if post.isSensitive - meta(property='og:image' content= avatarUrl) - meta(property='twitter:card' content='summary') - else - meta(property='og:image' content= post.files[0].thumbnailUrl) - meta(property='twitter:card' content='summary_large_image') - -block meta - if user.host || profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - - // todo - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) - - if !user.host - link(rel='alternate' href=url type='application/activity+json') diff --git a/packages/backend/src/server/web/views/gallery-post.tsx b/packages/backend/src/server/web/views/gallery-post.tsx new file mode 100644 index 0000000000..2bec2de930 --- /dev/null +++ b/packages/backend/src/server/web/views/gallery-post.tsx @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function GalleryPostPage(props: CommonProps<{ + galleryPost: Packed<'GalleryPost'>; + profile: MiUserProfile; +}>) { + function ogBlock() { + return ( + <> + + + {props.galleryPost.description != null ? : null} + + {props.galleryPost.isSensitive && props.galleryPost.user.avatarUrl ? ( + <> + + + + ) : null} + {!props.galleryPost.isSensitive && props.galleryPost.files != null ? ( + <> + + + + ) : null} + + ); + } + + function metaBlock() { + return ( + <> + {props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/info-card.pug b/packages/backend/src/server/web/views/info-card.pug deleted file mode 100644 index 2a4954ec8b..0000000000 --- a/packages/backend/src/server/web/views/info-card.pug +++ /dev/null @@ -1,50 +0,0 @@ -doctype html - -html - - head - meta(charset='utf-8') - meta(name='application-name' content='Misskey') - title= meta.name || host - style. - html, body { - margin: 0; - padding: 0; - min-height: 100vh; - background: #fff; - } - - #a { - display: block; - } - - #banner { - background-size: cover; - background-position: center center; - } - - #title { - display: inline-block; - margin: 24px; - padding: 0.5em 0.8em; - color: #fff; - background: rgba(0, 0, 0, 0.5); - font-weight: bold; - font-size: 1.3em; - } - - #content { - overflow: auto; - color: #353c3e; - } - - #description { - margin: 24px; - } - - body - a#a(href=`https://${host}` target="_blank") - header#banner(style=`background-image: url(${meta.bannerUrl})`) - div#title= meta.name || host - div#content - div#description!= meta.description diff --git a/packages/backend/src/server/web/views/info-card.tsx b/packages/backend/src/server/web/views/info-card.tsx new file mode 100644 index 0000000000..27be4c69e8 --- /dev/null +++ b/packages/backend/src/server/web/views/info-card.tsx @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { comment, CommonPropsMinimum } from '@/server/web/views/_.js'; +import type { MiMeta } from '@/models/Meta.js'; + +export function InfoCardPage(props: CommonPropsMinimum<{ + meta: MiMeta; +}>) { + // 変数名をsafeで始めることでエラーをスキップ + const safeDescription = props.meta.description; + + return ( + <> + {''} + {comment} + + + + + + {props.meta.name ?? props.config.url} + + + + + + +
+
{safeDescription}
+
+ + + + ); +} diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug deleted file mode 100644 index fb659ce171..0000000000 --- a/packages/backend/src/server/web/views/note.pug +++ /dev/null @@ -1,61 +0,0 @@ -extends ./base - -block vars - - const user = note.user; - - const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`; - - const url = `${config.url}/notes/${note.id}`; - - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - - const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive) - - const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive) - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= summary) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= summary) - meta(property='og:url' content= url) - if videos.length - each video in videos - meta(property='og:video:url' content= video.url) - meta(property='og:video:secure_url' content= video.url) - meta(property='og:video:type' content= video.type) - // FIXME: add width and height - // FIXME: add embed player for Twitter - if images.length - meta(property='twitter:card' content='summary_large_image') - each image in images - meta(property='og:image' content= image.url) - else - meta(property='twitter:card' content='summary') - meta(property='og:image' content= avatarUrl) - - -block meta - if user.host || isRenote || profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - meta(name='misskey:note-id' content=note.id) - - // todo - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) - - if note.prev - link(rel='prev' href=`${config.url}/notes/${note.prev}`) - if note.next - link(rel='next' href=`${config.url}/notes/${note.next}`) - - if !user.host - link(rel='alternate' href=url type='application/activity+json') - if note.uri - link(rel='alternate' href=note.uri type='application/activity+json') diff --git a/packages/backend/src/server/web/views/note.tsx b/packages/backend/src/server/web/views/note.tsx new file mode 100644 index 0000000000..803c3d2537 --- /dev/null +++ b/packages/backend/src/server/web/views/note.tsx @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; +import { isRenotePacked } from '@/misc/is-renote.js'; +import { getNoteSummary } from '@/misc/get-note-summary.js'; + +export function NotePage(props: CommonProps<{ + note: Packed<'Note'>; + profile: MiUserProfile; +}>) { + const title = props.note.user.name ? `${props.note.user.name} (@${props.note.user.username}${props.note.user.host ? `@${props.note.user.host}` : ''})` : `@${props.note.user.username}${props.note.user.host ? `@${props.note.user.host}` : ''}` + const isRenote = isRenotePacked(props.note); + const images = (props.note.files ?? []).filter(f => f.type.startsWith('image/')); + const videos = (props.note.files ?? []).filter(f => f.type.startsWith('video/')); + const summary = getNoteSummary(props.note); + + function ogBlock() { + return ( + <> + + + + + {videos.map(video => ( + <> + + + + {video.thumbnailUrl ? : null} + {video.properties.width != null ? : null} + {video.properties.height != null ? : null} + + ))} + {images.length > 0 ? ( + <> + + {images.map(image => ( + <> + + {image.properties.width != null ? : null} + {image.properties.height != null ? : null} + + ))} + + ) : ( + <> + + + + )} + + ); + } + + function metaBlock() { + return ( + <> + {props.note.user.host != null || isRenote || props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + + {props.federationEnabled ? ( + <> + {props.note.user.host == null ? : null} + {props.note.uri != null ? : null} + + ) : null} + + ); + } + + return ( + + ) +} diff --git a/packages/backend/src/server/web/views/oauth.pug b/packages/backend/src/server/web/views/oauth.pug deleted file mode 100644 index 4195ccc3a3..0000000000 --- a/packages/backend/src/server/web/views/oauth.pug +++ /dev/null @@ -1,11 +0,0 @@ -extends ./base - -block meta - //- Should be removed by the page when it loads, so that it won't needlessly - //- stay when user navigates away via the navigation bar - //- XXX: Remove navigation bar in auth page? - meta(name='misskey:oauth:transaction-id' content=transactionId) - meta(name='misskey:oauth:client-name' content=clientName) - if clientLogo - meta(name='misskey:oauth:client-logo' content=clientLogo) - meta(name='misskey:oauth:scope' content=scope) diff --git a/packages/backend/src/server/web/views/oauth.tsx b/packages/backend/src/server/web/views/oauth.tsx new file mode 100644 index 0000000000..d12b0d15fd --- /dev/null +++ b/packages/backend/src/server/web/views/oauth.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function OAuthPage(props: CommonProps<{ + transactionId: string; + clientName: string; + clientLogo?: string; + scope: string[]; +}>) { + + //- Should be removed by the page when it loads, so that it won't needlessly + //- stay when user navigates away via the navigation bar + //- XXX: Remove navigation bar in auth page? + function metaBlock() { + return ( + <> + + + {props.clientLogo ? : null} + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug deleted file mode 100644 index 03c50eca8a..0000000000 --- a/packages/backend/src/server/web/views/page.pug +++ /dev/null @@ -1,35 +0,0 @@ -extends ./base - -block vars - - const user = page.user; - - const title = page.title; - - const url = `${config.url}/@${user.username}/pages/${page.name}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= page.summary) - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content= page.summary) - meta(property='og:url' content= url) - meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl) - meta(property='twitter:card' content= page.eyeCatchingImage ? 'summary_large_image' : 'summary') - -block meta - if profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - meta(name='misskey:page-id' content=page.id) - - // todo - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/page.tsx b/packages/backend/src/server/web/views/page.tsx new file mode 100644 index 0000000000..d0484612df --- /dev/null +++ b/packages/backend/src/server/web/views/page.tsx @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function PagePage(props: CommonProps<{ + page: Packed<'Page'>; + profile: MiUserProfile; +}>) { + function ogBlock() { + return ( + <> + + + {props.page.summary != null ? : null} + + {props.page.eyeCatchingImage != null ? ( + <> + + + + ) : props.page.user.avatarUrl ? ( + <> + + + + ) : null} + + ); + } + + function metaBlock() { + return ( + <> + {props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/reversi-game.pug b/packages/backend/src/server/web/views/reversi-game.pug deleted file mode 100644 index 0b5ffb2bb0..0000000000 --- a/packages/backend/src/server/web/views/reversi-game.pug +++ /dev/null @@ -1,20 +0,0 @@ -extends ./base - -block vars - - const user1 = game.user1; - - const user2 = game.user2; - - const title = `${user1.username} vs ${user2.username}`; - - const url = `${config.url}/reversi/g/${game.id}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content='⚫⚪Misskey Reversi⚪⚫') - -block og - meta(property='og:type' content='article') - meta(property='og:title' content= title) - meta(property='og:description' content='⚫⚪Misskey Reversi⚪⚫') - meta(property='og:url' content= url) - meta(property='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/reversi-game.tsx b/packages/backend/src/server/web/views/reversi-game.tsx new file mode 100644 index 0000000000..22609311fd --- /dev/null +++ b/packages/backend/src/server/web/views/reversi-game.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function ReversiGamePage(props: CommonProps<{ + reversiGame: Packed<'ReversiGameDetailed'>; +}>) { + const title = `${props.reversiGame.user1.username} vs ${props.reversiGame.user2.username}`; + const description = `⚫⚪Misskey Reversi⚪⚫`; + + function ogBlock() { + return ( + <> + + + + + + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug deleted file mode 100644 index 2b0a7bab5c..0000000000 --- a/packages/backend/src/server/web/views/user.pug +++ /dev/null @@ -1,43 +0,0 @@ -extends ./base - -block vars - - const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`; - - const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`; - -block title - = `${title} | ${instanceName}` - -block desc - meta(name='description' content= profile.description) - -block og - meta(property='og:type' content='blog') - meta(property='og:title' content= title) - meta(property='og:description' content= profile.description) - meta(property='og:url' content= url) - meta(property='og:image' content= avatarUrl) - meta(property='twitter:card' content='summary') - -block meta - if user.host || profile.noCrawle - meta(name='robots' content='noindex') - if profile.preventAiLearning - meta(name='robots' content='noimageai') - meta(name='robots' content='noai') - - meta(name='misskey:user-username' content=user.username) - meta(name='misskey:user-id' content=user.id) - - if profile.twitter - meta(name='twitter:creator' content=`@${profile.twitter.screenName}`) - - if !sub - if !user.host - link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json') - if user.uri - link(rel='alternate' href=user.uri type='application/activity+json') - if profile.url - link(rel='alternate' href=profile.url type='text/html') - - each m in me - link(rel='me' href=`${m}`) diff --git a/packages/backend/src/server/web/views/user.tsx b/packages/backend/src/server/web/views/user.tsx new file mode 100644 index 0000000000..76c2633ab9 --- /dev/null +++ b/packages/backend/src/server/web/views/user.tsx @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { CommonProps } from '@/server/web/views/_.js'; +import { Layout } from '@/server/web/views/base.js'; + +export function UserPage(props: CommonProps<{ + user: Packed<'UserDetailed'>; + profile: MiUserProfile; + sub?: string; +}>) { + const title = props.user.name ? `${props.user.name} (@${props.user.username}${props.user.host ? `@${props.user.host}` : ''})` : `@${props.user.username}${props.user.host ? `@${props.user.host}` : ''}`; + const me = props.profile.fields + ? props.profile.fields + .filter(field => field.value != null && field.value.match(/^https?:/)) + .map(field => field.value) + : []; + + function ogBlock() { + return ( + <> + + + {props.user.description != null ? : null} + + + + + ); + } + + function metaBlock() { + return ( + <> + {props.user.host != null || props.profile.noCrawle ? : null} + {props.profile.preventAiLearning ? ( + <> + + + + ) : null} + + + + {props.sub == null && props.federationEnabled ? ( + <> + {props.user.host == null ? : null} + {props.user.uri != null ? : null} + {props.profile.url != null ? : null} + + ) : null} + + {me.map((url) => ( + + ))} + + ); + } + + return ( + + + ); +} diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 5d5f1e3b71..24654b0017 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -12,6 +12,8 @@ * quote - 投稿が引用Renoteされた * reaction - 投稿にリアクションされた * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した + * scheduledNotePosted - 予約したノートが投稿された + * scheduledNotePostFailed - 予約したノートの投稿に失敗した * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された * roleAssigned - ロールが付与された @@ -32,6 +34,8 @@ export const notificationTypes = [ 'quote', 'reaction', 'pollEnded', + 'scheduledNotePosted', + 'scheduledNotePostFailed', 'receiveFollowRequest', 'followRequestAccepted', 'roleAssigned', @@ -54,6 +58,8 @@ export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; +export const noteReactionAcceptances = ['likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote', null] as const; + export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const followingVisibilities = ['public', 'followers', 'private'] as const; diff --git a/packages/backend/test-federation/.config/dummy.yml b/packages/backend/test-federation/.config/dummy.yml new file mode 100644 index 0000000000..841cab9783 --- /dev/null +++ b/packages/backend/test-federation/.config/dummy.yml @@ -0,0 +1,2 @@ +url: https://example.com/ +port: 3000 diff --git a/packages/backend/test-federation/.config/example.config.json b/packages/backend/test-federation/.config/example.config.json new file mode 100644 index 0000000000..2035d1a200 --- /dev/null +++ b/packages/backend/test-federation/.config/example.config.json @@ -0,0 +1,29 @@ +{ + "url": "https://${HOST}/", + "port": 3000, + "db": { + "host": "db.${HOST}", + "port": 5432, + "db": "misskey", + "user": "postgres", + "pass": "postgres" + }, + "dbReplications": false, + "trustProxy": true, + "redis": { + "host": "redis.test", + "port": 6379 + }, + "id": "aidx", + "proxyBypassHosts": [ + "api.deepl.com", + "api-free.deepl.com", + "www.recaptcha.net", + "hcaptcha.com", + "challenges.cloudflare.com" + ], + "allowedPrivateNetworks": [ + "127.0.0.1/32", + "172.20.0.0/16" + ] +} diff --git a/packages/backend/test-federation/.config/example.default.yml b/packages/backend/test-federation/.config/example.default.yml deleted file mode 100644 index 28d51ac86e..0000000000 --- a/packages/backend/test-federation/.config/example.default.yml +++ /dev/null @@ -1,24 +0,0 @@ -url: https://${HOST}/ -port: 3000 -db: - host: db.${HOST} - port: 5432 - db: misskey - user: postgres - pass: postgres -dbReplications: false -redis: - host: redis.test - port: 6379 -id: 'aidx' -proxyBypassHosts: - - api.deepl.com - - api-free.deepl.com - - www.recaptcha.net - - hcaptcha.com - - challenges.cloudflare.com -proxyRemoteFiles: true -signToActivityPubGet: true -allowedPrivateNetworks: - - 127.0.0.1/32 - - 172.20.0.0/16 diff --git a/packages/backend/test-federation/README.md b/packages/backend/test-federation/README.md index 967d51f085..4ea88c1b80 100644 --- a/packages/backend/test-federation/README.md +++ b/packages/backend/test-federation/README.md @@ -10,15 +10,15 @@ cd packages/backend/test-federation First, you need to start servers by executing following commands: ```sh bash ./setup.sh -docker compose up --scale tester=0 +NODE_VERSION=22 docker compose up --scale tester=0 ``` Then you can run all tests by a following command: ```sh -docker compose run --no-deps --rm tester +NODE_VERSION=22 docker compose run --no-deps --rm tester ``` For testing a specific file, run a following command: ```sh -docker compose run --no-deps --rm tester -- pnpm -F backend test:fed packages/backend/test-federation/test/user.test.ts +NODE_VERSION=22 docker compose run --no-deps --rm tester -- pnpm -F backend test:fed packages/backend/test-federation/test/user.test.ts ``` diff --git a/packages/backend/test-federation/compose.a.yml b/packages/backend/test-federation/compose.a.yml index 6a305b404c..ec9a2cf2af 100644 --- a/packages/backend/test-federation/compose.a.yml +++ b/packages/backend/test-federation/compose.a.yml @@ -37,8 +37,8 @@ services: - internal_network_a volumes: - type: bind - source: ./.config/a.test.default.yml - target: /misskey/.config/default.yml + source: ./.config/a.test.config.json + target: /misskey/built/._config_.json read_only: true db.a.test: @@ -50,7 +50,7 @@ services: volumes: - type: bind source: ./volumes/db.a - target: /var/lib/postgresql/data + target: /var/lib/postgresql bind: create_host_path: true diff --git a/packages/backend/test-federation/compose.b.yml b/packages/backend/test-federation/compose.b.yml index 1158b53bae..9221934406 100644 --- a/packages/backend/test-federation/compose.b.yml +++ b/packages/backend/test-federation/compose.b.yml @@ -37,8 +37,8 @@ services: - internal_network_b volumes: - type: bind - source: ./.config/b.test.default.yml - target: /misskey/.config/default.yml + source: ./.config/b.test.config.json + target: /misskey/built/._config_.json read_only: true db.b.test: @@ -50,7 +50,7 @@ services: volumes: - type: bind source: ./volumes/db.b - target: /var/lib/postgresql/data + target: /var/lib/postgresql bind: create_host_path: true diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml index 33e75841c0..ede9780847 100644 --- a/packages/backend/test-federation/compose.tpl.yml +++ b/packages/backend/test-federation/compose.tpl.yml @@ -12,7 +12,7 @@ services: retries: 20 misskey: - image: node:20 + image: node:${NODE_VERSION} env_file: - ./.config/docker.env environment: @@ -21,6 +21,10 @@ services: - type: bind source: ../../../built target: /misskey/built + read_only: false + - type: bind + source: ./.config/dummy.yml + target: /misskey/.config/default.yml read_only: true - type: bind source: ../assets @@ -30,6 +34,10 @@ services: source: ../built target: /misskey/packages/backend/built read_only: true + - type: bind + source: ../src-js + target: /misskey/packages/backend/src-js + read_only: true - type: bind source: ../migration target: /misskey/packages/backend/migration @@ -42,6 +50,10 @@ services: source: ../package.json target: /misskey/packages/backend/package.json read_only: true + - type: bind + source: ../scripts/compile_config.js + target: /misskey/packages/backend/scripts/compile_config.js + read_only: true - type: bind source: ../../misskey-js/built target: /misskey/packages/misskey-js/built @@ -51,12 +63,12 @@ services: target: /misskey/packages/misskey-js/package.json read_only: true - type: bind - source: ../../misskey-mahjong/built - target: /misskey/packages/misskey-mahjong/built + source: ../../i18n/built + target: /misskey/packages/i18n/built read_only: true - type: bind - source: ../../misskey-mahjong/package.json - target: /misskey/packages/misskey-mahjong/package.json + source: ../../i18n/package.json + target: /misskey/packages/i18n/package.json read_only: true - type: bind source: ../../misskey-reversi/built @@ -66,6 +78,15 @@ services: source: ../../misskey-reversi/package.json target: /misskey/packages/misskey-reversi/package.json read_only: true + - type: bind + source: ../../misskey-mahjong/built + target: /misskey/packages/misskey-mahjong/built + read_only: true + - type: bind + source: ../../misskey-mahjong/package.json + target: /misskey/packages/misskey-mahjong/package.json + read_only: true + - type: bind source: ../../../healthcheck.sh target: /misskey/healthcheck.sh @@ -83,8 +104,8 @@ services: target: /misskey/pnpm-workspace.yaml read_only: true - type: bind - source: ../../../scripts/dependency-patches - target: /misskey/scripts/dependency-patches + source: ../../../patches + target: /misskey/patches read_only: true - type: bind source: ./certificates/rootCA.crt @@ -103,7 +124,7 @@ services: retries: 20 db: - image: postgres:15-alpine + image: postgres:18-alpine env_file: - ./.config/docker.env volumes: diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml index dfa51b940a..4d1b4b0d60 100644 --- a/packages/backend/test-federation/compose.yml +++ b/packages/backend/test-federation/compose.yml @@ -16,7 +16,7 @@ services: " tester: - image: node:20 + image: node:${NODE_VERSION} depends_on: a.test: condition: service_healthy @@ -50,6 +50,14 @@ services: source: ../jest.config.fed.cjs target: /misskey/packages/backend/jest.config.fed.cjs read_only: true + - type: bind + source: ../jest.js + target: /misskey/packages/backend/jest.js + read_only: true + - type: bind + source: ../scripts/compile_config.js + target: /misskey/packages/backend/scripts/compile_config.js + read_only: true - type: bind source: ../../misskey-js/built target: /misskey/packages/misskey-js/built @@ -58,6 +66,14 @@ services: source: ../../misskey-js/package.json target: /misskey/packages/misskey-js/package.json read_only: true + - type: bind + source: ../../i18n/built + target: /misskey/packages/i18n/built + read_only: true + - type: bind + source: ../../i18n/package.json + target: /misskey/packages/i18n/package.json + read_only: true - type: bind source: ../../../package.json target: /misskey/package.json @@ -71,8 +87,8 @@ services: target: /misskey/pnpm-workspace.yaml read_only: true - type: bind - source: ../../../scripts/dependency-patches - target: /misskey/scripts/dependency-patches + source: ../../../patches + target: /misskey/patches read_only: true - type: bind source: ./certificates/rootCA.crt @@ -89,7 +105,7 @@ services: command: pnpm -F backend test:fed daemon: - image: node:20 + image: node:${NODE_VERSION} depends_on: redis.test: condition: service_healthy @@ -119,15 +135,15 @@ services: target: /misskey/pnpm-workspace.yaml read_only: true - type: bind - source: ../../../scripts/dependency-patches - target: /misskey/scripts/dependency-patches + source: ../../../patches + target: /misskey/patches read_only: true working_dir: /misskey command: > bash -c " npm install -g pnpm pnpm -F backend i --frozen-lockfile - pnpm exec tsc -p ./packages/backend/test-federation + pnpm exec tsgo -p ./packages/backend/test-federation node ./packages/backend/test-federation/built/daemon.js " diff --git a/packages/backend/test-federation/setup.sh b/packages/backend/test-federation/setup.sh index 1bc3a2a87c..15aa2eee7f 100644 --- a/packages/backend/test-federation/setup.sh +++ b/packages/backend/test-federation/setup.sh @@ -28,7 +28,7 @@ function generate { -days 500 if [ ! -f .config/docker.env ]; then cp .config/example.docker.env .config/docker.env; fi if [ ! -f .config/$1.conf ]; then sed "s/\${HOST}/$1/g" .config/example.conf > .config/$1.conf; fi - if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.default.yml > .config/$1.default.yml; fi + if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.config.json > .config/$1.config.json; fi } generate a.test diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts index 1584f9587e..a339cd86d2 100644 --- a/packages/backend/test-federation/test/note.test.ts +++ b/packages/backend/test-federation/test/note.test.ts @@ -63,7 +63,6 @@ describe('Note', () => { deepStrictEqualWithExcludedFields(note, resolvedNote, [ 'id', 'emojis', - 'reactionAcceptance', 'replyId', 'reply', 'userId', @@ -105,7 +104,6 @@ describe('Note', () => { deepStrictEqualWithExcludedFields(note, resolvedNote, [ 'id', 'emojis', - 'reactionAcceptance', 'renoteId', 'renote', 'userId', diff --git a/packages/backend/test-federation/test/user.test.ts b/packages/backend/test-federation/test/user.test.ts index ee69e857bc..ebbe9ff5ba 100644 --- a/packages/backend/test-federation/test/user.test.ts +++ b/packages/backend/test-federation/test/user.test.ts @@ -380,9 +380,7 @@ describe('User', () => { strictEqual(followers.length, 1); // followed by Bob await alice.client.request('i/delete-account', { password: alice.password }); - // NOTE: user deletion query is slow - // FIXME: ensure user is removed successfully - await sleep(10000); + await sleep(); const following = await bob.client.request('users/following', { userId: bob.id }); strictEqual(following.length, 0); // no following relation @@ -480,9 +478,7 @@ describe('User', () => { strictEqual(followers.length, 1); // followed by Bob await aAdmin.client.request('admin/suspend-user', { userId: alice.id }); - // NOTE: user deletion query is slow - // FIXME: ensure user is removed successfully - await sleep(10000); + await sleep(); const following = await bob.client.request('users/following', { userId: bob.id }); strictEqual(following.length, 0); // no following relation diff --git a/packages/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts index 2779eb7e81..6f09f13f17 100644 --- a/packages/backend/test-federation/test/utils.ts +++ b/packages/backend/test-federation/test/utils.ts @@ -68,7 +68,6 @@ async function createAdmin(host: Host): Promise { ADMIN_CACHE.set(host, { id: res.id, - // @ts-expect-error FIXME: openapi-typescript generates incorrect response type for this endpoint, so ignore this i: res.token, }); return res as Misskey.entities.SignupResponse; @@ -79,6 +78,9 @@ async function createAdmin(host: Host): Promise { if (err.info.e.message === 'access denied') return undefined; @@ -187,7 +189,8 @@ export async function uploadFile( path = '../../test/resources/192.jpg', ): Promise { const filename = path.split('/').pop() ?? 'untitled'; - const blob = new Blob([await readFile(join(__dirname, path))]); + const buffer = await readFile(join(__dirname, path)); + const blob = new Blob([new Uint8Array(buffer)]); const body = new FormData(); body.append('i', user.i); @@ -231,30 +234,26 @@ export async function isFired[0]) => boolean, params?: Misskey.Channels[C]['params'], ): Promise { - return new Promise(async (resolve, reject) => { - const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + try { const connection = stream.useChannel(channel, params); - connection.on(type as any, ((msg: any) => { - if (cond(msg)) { - stream.close(); - clearTimeout(timer); - resolve(true); - } - }) as any); - let timer: NodeJS.Timeout | undefined; - - await trigger().then(() => { - timer = setTimeout(() => { - stream.close(); - resolve(false); - }, 500); - }).catch(err => { - stream.close(); - clearTimeout(timer); - reject(err); + const receivePromise = new Promise((resolve) => { + connection.on(type as never, ((msg: any) => { + if (cond(msg)) { + resolve(true); + } + }) as any); }); - }); + + await trigger(); + return await Promise.race([ + receivePromise, + sleep(500).then(() => false), + ]); + } finally { + stream.close(); + } }; export async function isNoteUpdatedEventFired( @@ -264,30 +263,27 @@ export async function isNoteUpdatedEventFired( trigger: () => Promise, cond: (msg: Parameters[0]) => boolean, ): Promise { - return new Promise(async (resolve, reject) => { - const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + try { stream.send('s', { id: noteId }); - stream.on('noteUpdated', msg => { - if (cond(msg)) { - stream.close(); - clearTimeout(timer); - resolve(true); - } + + const receivePromise = new Promise((resolve) => { + stream.on('noteUpdated', msg => { + if (cond(msg)) { + resolve(true); + } + }); }); - let timer: NodeJS.Timeout | undefined; + await trigger(); - await trigger().then(() => { - timer = setTimeout(() => { - stream.close(); - resolve(false); - }, 500); - }).catch(err => { - stream.close(); - clearTimeout(timer); - reject(err); - }); - }); + return await Promise.race([ + receivePromise, + sleep(500).then(() => false), + ]); + } finally { + stream.close(); + } }; export async function assertNotificationReceived( diff --git a/packages/backend/test-federation/tsconfig.json b/packages/backend/test-federation/tsconfig.json index 3a1cb3b9f3..8e74a62e81 100644 --- a/packages/backend/test-federation/tsconfig.json +++ b/packages/backend/test-federation/tsconfig.json @@ -13,12 +13,12 @@ /* Language and Environment */ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ + "jsx": "react-jsx", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + "jsxImportSource": "@kitajs/html", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc index eeac7eabc6..3859603da3 100644 --- a/packages/backend/test-server/.swcrc +++ b/packages/backend/test-server/.swcrc @@ -13,7 +13,7 @@ "experimental": { "keepImportAssertions": true }, - "baseUrl": "../built", + "baseUrl": "../src-js", "paths": { "@/*": ["*"] }, diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json index 10313699c2..7ed7c10ed7 100644 --- a/packages/backend/test-server/tsconfig.json +++ b/packages/backend/test-server/tsconfig.json @@ -23,6 +23,8 @@ "emitDecoratorMetadata": true, "resolveJsonModule": true, "isolatedModules": true, + "jsx": "react-jsx", + "jsxImportSource": "@kitajs/html", "rootDir": "../src", "baseUrl": "./", "paths": { diff --git a/packages/backend/test/compose.yml b/packages/backend/test/compose.yml index 6593fc33dd..4f1dba6428 100644 --- a/packages/backend/test/compose.yml +++ b/packages/backend/test/compose.yml @@ -5,9 +5,17 @@ services: - "127.0.0.1:56312:6379" dbtest: - image: postgres:15 + image: postgres:18 ports: - "127.0.0.1:54312:5432" environment: POSTGRES_DB: "test-misskey" POSTGRES_HOST_AUTH_METHOD: trust + + meilisearchtest: + image: getmeili/meilisearch:v1.3.4 + ports: + - "127.0.0.1:57712:7700" + environment: + - MEILI_NO_ANALYTICS=true + - MEILI_ENV=development diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 4dbeacf925..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', @@ -673,7 +694,6 @@ describe('アンテナ', () => { assert.deepStrictEqual(response, expected); }); - test.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { }); test.each([ { label: 'ID指定', offsetBy: 'id' }, diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 7ae1ee4523..fe9a217ee8 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -363,14 +363,11 @@ describe('クリップ', () => { const clipLimit = DEFAULT_POLICIES.clipLimit; const clips = await createMany({}, clipLimit); const res = await list({ - parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる + parameters: { limit: clips.length }, }); - // 返ってくる配列には順序保障がないのでidでソートして厳密比較 - assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - clips.sort(compareBy(s => s.id)), - ); + // 作成responseの配列には順序保障がないのでidでソートして厳密比較 + assert.deepStrictEqual(res.toReversed(), clips.sort(compareBy(s => s.id))); }); test('の一覧が取得できる(空)', async () => { @@ -909,7 +906,7 @@ describe('クリップ', () => { assert.deepStrictEqual(res.map(x => x.id), [aliceNote.id]); }); - test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートはhideされて返ってくる)', async () => { + test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートは含まれない)', async () => { const publicClip = await create({ isPublic: true }); await addNote({ clipId: publicClip.id, noteId: aliceNote.id }); await addNote({ clipId: publicClip.id, noteId: aliceHomeNote.id }); @@ -919,8 +916,6 @@ describe('クリップ', () => { const res = await notes({ clipId: publicClip.id }, { user: undefined }); const expects = [ aliceNote, aliceHomeNote, - // 認証なしだと非公開ノートは結果には含むけどhideされる。 - hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote), ]; assert.deepStrictEqual( res.sort(compareBy(s => s.id)).map(x => x.id), diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index b91d77c398..469f19e2b9 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -9,9 +9,9 @@ import * as assert from 'assert'; // node-fetch only supports it's own Blob yet // https://github.com/node-fetch/node-fetch/pull/1664 import { Blob } from 'node-fetch'; -import { MiUser } from '@/models/_.js'; import { api, castAsError, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; +import { MiUser } from '@/models/_.js'; describe('Endpoints', () => { let alice: misskey.entities.SignupResponse; @@ -24,6 +24,7 @@ describe('Endpoints', () => { bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); dave = await signup({ username: 'dave' }); + await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse); }, 1000 * 60 * 2); describe('signup', () => { @@ -572,19 +573,10 @@ describe('Endpoints', () => { describe('drive', () => { test('ドライブ情報を取得できる', async () => { - await uploadFile(alice, { - blob: new Blob([new Uint8Array(256)]), - }); - await uploadFile(alice, { - blob: new Blob([new Uint8Array(512)]), - }); - await uploadFile(alice, { - blob: new Blob([new Uint8Array(1024)]), - }); const res = await api('drive', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - expect(res.body).toHaveProperty('usage', 1792); + expect(res.body).toHaveProperty('usage', 0); }); }); diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts index 4bcecc9716..19433f3c88 100644 --- a/packages/backend/test/e2e/exports.ts +++ b/packages/backend/test/e2e/exports.ts @@ -16,7 +16,7 @@ describe('export-clips', () => { let bob: misskey.entities.SignupResponse; // XXX: Any better way to get the result? - async function pollFirstDriveFile() { + async function pollFirstDriveFile(): Promise { while (true) { const files = (await api('drive/files', {}, alice)).body; if (!files.length) { @@ -168,7 +168,36 @@ describe('export-clips', () => { assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2'); }); - test('Clipping other user\'s note', async () => { + test('Clipping other user\'s note (followers only notes are excluded when not following)', async () => { + const res = await api('clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note = await post(bob, { + text: 'baz', + visibility: 'followers', + }); + + const res2 = await api('clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res2.status, 204); + + const res3 = await api('i/export-clips', {}, alice); + assert.strictEqual(res3.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].clipNotes.length, 0); + }); + + test('Clipping other user\'s note (followers only notes are included when following)', async () => { + // Alice follows Bob + await api('following/create', { userId: bob.id }, alice); + const res = await api('clips/create', { name: 'kawaii', description: 'kawaii', diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 740295bda8..f00843de10 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; +import { api, channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js'; import type * as misskey from 'misskey-js'; @@ -73,11 +73,12 @@ describe('Webリソース', () => { }; const metaTag = (res: SimpleGetResponse, key: string, superkey = 'name'): string => { - return res.body.window.document.querySelector('meta[' + superkey + '="' + key + '"]')?.content; + return res.body.querySelector('meta[' + superkey + '="' + key + '"]')?.attributes.content; }; beforeAll(async () => { alice = await signup({ username: 'alice' }); + await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse); aliceUploadedFile = (await uploadFile(alice)).body; alicesPost = await post(alice, { text: 'test', diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts index 5d0c70a3c2..02582ae815 100644 --- a/packages/backend/test/e2e/ff-visibility.ts +++ b/packages/backend/test/e2e/ff-visibility.ts @@ -16,6 +16,7 @@ describe('FF visibility', () => { beforeAll(async () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); + await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse); }, 1000 * 60 * 2); test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => { diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts index f639f90ea6..67a9026eb5 100644 --- a/packages/backend/test/e2e/oauth.ts +++ b/packages/backend/test/e2e/oauth.ts @@ -19,7 +19,7 @@ import { ResourceOwnerPassword, } from 'simple-oauth2'; import pkceChallenge from 'pkce-challenge'; -import { JSDOM } from 'jsdom'; +import * as htmlParser from 'node-html-parser'; import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify'; import { api, port, sendEnvUpdateRequest, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; @@ -28,6 +28,7 @@ const host = `http://127.0.0.1:${port}`; const clientPort = port + 1; const redirect_uri = `http://127.0.0.1:${clientPort}/redirect`; +const redirect_uri2 = `http://127.0.0.1:${clientPort}/redirect2`; const basicAuthParams: AuthorizationParamsExtended = { redirect_uri, @@ -73,11 +74,11 @@ const clientConfig: ModuleOptions<'client_id'> = { }; function getMeta(html: string): { transactionId: string | undefined, clientName: string | undefined, clientLogo: string | undefined } { - const fragment = JSDOM.fragment(html); + const doc = htmlParser.parse(`
${html}
`); return { - transactionId: fragment.querySelector('meta[name="misskey:oauth:transaction-id"]')?.content, - clientName: fragment.querySelector('meta[name="misskey:oauth:client-name"]')?.content, - clientLogo: fragment.querySelector('meta[name="misskey:oauth:client-logo"]')?.content, + transactionId: doc.querySelector('meta[name="misskey:oauth:transaction-id"]')?.attributes.content, + clientName: doc.querySelector('meta[name="misskey:oauth:client-name"]')?.attributes.content, + clientLogo: doc.querySelector('meta[name="misskey:oauth:client-logo"]')?.attributes.content, }; } @@ -148,7 +149,7 @@ function assertIndirectError(response: Response, error: string): void { async function assertDirectError(response: Response, status: number, error: string): Promise { assert.strictEqual(response.status, status); - const data = await response.json(); + const data = await response.json() as any; assert.strictEqual(data.error, error); } @@ -704,7 +705,7 @@ describe('OAuth', () => { const response = await fetch(new URL('.well-known/oauth-authorization-server', host)); assert.strictEqual(response.status, 200); - const body = await response.json(); + const body = await response.json() as any; assert.strictEqual(body.issuer, 'http://misskey.local'); assert.ok(body.scopes_supported.includes('write:notes')); }); @@ -807,65 +808,19 @@ describe('OAuth', () => { }); }); - // https://indieauth.spec.indieweb.org/#client-information-discovery describe('Client Information Discovery', () => { - describe('Redirection', () => { - const tests: Record void> = { - 'Read HTTP header': reply => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(` - -
Misklient - `); - }, - 'Mixed links': reply => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(` - - -
Misklient - `); - }, - 'Multiple items in Link header': reply => { - reply.header('Link', '; rel="redirect_uri",; rel="redirect_uri"'); - reply.send(` - -
Misklient - `); - }, - 'Multiple items in HTML': reply => { - reply.send(` - - - -
Misklient - `); - }, - }; - - for (const [title, replyFunc] of Object.entries(tests)) { - test(title, async () => { - sender = replyFunc; - - const client = new AuthorizationCode(clientConfig); - - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - assert.strictEqual(response.status, 200); - }); - } - - test('No item', async () => { + // https://indieauth.spec.indieweb.org/#client-information-discovery + describe('JSON client metadata (11 July 2024)', () => { + test('Read JSON document', async () => { sender = (reply): void => { - reply.send(` - -
Misklient - `); + reply.header('content-type', 'application/json'); + reply.send({ + client_id: `http://127.0.0.1:${clientPort}/`, + client_uri: `http://127.0.0.1:${clientPort}/`, + client_name: 'Misklient JSON', + logo_uri: '/logo.png', + redirect_uris: ['/redirect'], + }); }; const client = new AuthorizationCode(clientConfig); @@ -877,119 +832,294 @@ describe('OAuth', () => { code_challenge: 'code', code_challenge_method: 'S256', } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + const meta = getMeta(await response.text()); + assert.strictEqual(meta.clientName, 'Misklient JSON'); + assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`); + }); - // direct error because there's no redirect URI to ping + test('Merge Link header redirect_uri with JSON redirect_uris', async () => { + sender = (reply): void => { + reply.header('Link', '; rel="redirect_uri"'); + reply.header('content-type', 'application/json'); + reply.send({ + client_id: `http://127.0.0.1:${clientPort}/`, + client_uri: `http://127.0.0.1:${clientPort}/`, + client_name: 'Misklient JSON', + redirect_uris: ['/redirect'], + }); + }; + + const client = new AuthorizationCode(clientConfig); + + const ok1 = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(ok1.status, 200); + + const ok2 = await fetch(client.authorizeURL({ + redirect_uri: redirect_uri2, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(ok2.status, 200); + }); + + test('Reject when client_id does not match retrieved URL', async () => { + sender = (reply): void => { + reply.header('content-type', 'application/json'); + reply.send({ + client_id: `http://127.0.0.1:${clientPort}/mismatch`, + client_uri: `http://127.0.0.1:${clientPort}/`, + redirect_uris: ['/redirect'], + }); + }; + + const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + await assertDirectError(response, 400, 'invalid_request'); + }); + + test('Reject when client_uri is not a prefix of client_id', async () => { + sender = (reply): void => { + reply.header('content-type', 'application/json'); + reply.send({ + client_id: `http://127.0.0.1:${clientPort}/`, + client_uri: `http://127.0.0.1:${clientPort}/no-prefix/`, + redirect_uris: ['/redirect'], + }); + }; + + const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + await assertDirectError(response, 400, 'invalid_request'); + }); + + test('Reject when JSON metadata has no redirect_uris and no Link header', async () => { + sender = (reply): void => { + reply.header('content-type', 'application/json'); + reply.send({ + client_id: `http://127.0.0.1:${clientPort}/`, + client_uri: `http://127.0.0.1:${clientPort}/`, + client_name: 'Misklient JSON', + }); + }; + + const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); await assertDirectError(response, 400, 'invalid_request'); }); }); - test('Disallow loopback', async () => { - await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' }); + // https://indieauth.spec.indieweb.org/20220212/#client-information-discovery + describe('HTML link client metadata (12 Feb 2022)', () => { + describe('Redirection', () => { + const tests: Record void> = { + 'Read HTTP header': reply => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(` + +
Misklient + `); + }, + 'Mixed links': reply => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(` + + +
Misklient + `); + }, + 'Multiple items in Link header': reply => { + reply.header('Link', '; rel="redirect_uri",; rel="redirect_uri"'); + reply.send(` + +
Misklient + `); + }, + 'Multiple items in HTML': reply => { + reply.send(` + + + +
Misklient + `); + }, + }; - const client = new AuthorizationCode(clientConfig); - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - await assertDirectError(response, 400, 'invalid_request'); - }); + for (const [title, replyFunc] of Object.entries(tests)) { + test(title, async () => { + sender = replyFunc; - test('Missing name', async () => { - sender = (reply): void => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(); - }; + const client = new AuthorizationCode(clientConfig); - const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + }); + } - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - assert.strictEqual(response.status, 200); - assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`); - }); + test('No item', async () => { + sender = (reply): void => { + reply.send(` + +
Misklient + `); + }; - test('With Logo', async () => { - sender = (reply): void => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(` - - - `); - reply.send(); - }; + const client = new AuthorizationCode(clientConfig); - const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - assert.strictEqual(response.status, 200); - const meta = getMeta(await response.text()); - assert.strictEqual(meta.clientName, 'Misklient'); - assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`); - }); + // direct error because there's no redirect URI to ping + await assertDirectError(response, 400, 'invalid_request'); + }); + }); - test('Missing Logo', async () => { - sender = (reply): void => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(` - -
Misklient - `); - reply.send(); - }; - const client = new AuthorizationCode(clientConfig); + test('Disallow loopback', async () => { + await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' }); - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - assert.strictEqual(response.status, 200); - const meta = getMeta(await response.text()); - assert.strictEqual(meta.clientName, 'Misklient'); - assert.strictEqual(meta.clientLogo, undefined); - }); + const client = new AuthorizationCode(clientConfig); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + await assertDirectError(response, 400, 'invalid_request'); + }); - test('Mismatching URL in h-app', async () => { - sender = (reply): void => { - reply.header('Link', '; rel="redirect_uri"'); - reply.send(` - -
Misklient - `); - reply.send(); - }; + test('Missing name', async () => { + sender = (reply): void => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(); + }; - const client = new AuthorizationCode(clientConfig); + const client = new AuthorizationCode(clientConfig); - const response = await fetch(client.authorizeURL({ - redirect_uri, - scope: 'write:notes', - state: 'state', - code_challenge: 'code', - code_challenge_method: 'S256', - } as AuthorizationParamsExtended)); - assert.strictEqual(response.status, 200); - assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`); + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`); + }); + + test('With Logo', async () => { + sender = (reply): void => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(` + + + `); + reply.send(); + }; + + const client = new AuthorizationCode(clientConfig); + + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + const meta = getMeta(await response.text()); + assert.strictEqual(meta.clientName, 'Misklient'); + assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`); + }); + + test('Missing Logo', async () => { + sender = (reply): void => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(` + +
Misklient + `); + reply.send(); + }; + + const client = new AuthorizationCode(clientConfig); + + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + const meta = getMeta(await response.text()); + assert.strictEqual(meta.clientName, 'Misklient'); + assert.strictEqual(meta.clientLogo, undefined); + }); + + test('Mismatching URL in h-app', async () => { + sender = (reply): void => { + reply.header('Link', '; rel="redirect_uri"'); + reply.send(` + +
Misklient + `); + reply.send(); + }; + + const client = new AuthorizationCode(clientConfig); + + const response = await fetch(client.authorizeURL({ + redirect_uri, + scope: 'write:notes', + state: 'state', + code_challenge: 'code', + code_challenge_method: 'S256', + } as AuthorizationParamsExtended)); + assert.strictEqual(response.status, 200); + assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`); + }); }); }); diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index d6d2cb33f0..4fd826100d 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -3,1456 +3,3326 @@ * 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 { 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() { return randomString() + '.example.com'; } -function waitForPushToTl() { - return setTimeout(500); +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); } -let redisForTimelines: Redis; +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', () => { - beforeAll(() => { + 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 }, + ])('Timelines (enableFanoutTimeline: $enableFanoutTimeline)', ({ enableFanoutTimeline }) => { + function waitForPushToTl() { + return setTimeout(250); + } + + beforeAll(async () => { + await api('admin/update-meta', { enableFanoutTimeline }, root); + }, 1000 * 60 * 2); + + describe('Home TL', () => { + test('自分の visibility: followers なノートが含まれる', async () => { + const [alice] = await Promise.all([signup()]); + + const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); + }); + + test('フォローしているユーザーのノートが含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi' }); + const carolNote = await post(carol, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + const carolNote = await post(carol, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: carol.id }, bob); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + await api('following/create', { userId: carol.id }, bob); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + 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, 'hi'); + }); + + test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); + }); + + test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + }); + + test('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }); + + test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/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); + }); + + test('自分の他人への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi' }); + const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + }); + + test('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('[withRenotes: false] フォローしているユーザーの投稿が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi' }); + const carolNote = await post(carol, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { + limit: 100, + withRenotes: false, + }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('[withRenotes: false] フォローしているユーザーのファイルのみの投稿が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const [bobFile, carolFile] = await Promise.all([ + uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), + uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), + ]); + const bobNote = await post(bob, { fileIds: [bobFile.id] }); + const carolNote = await post(carol, { fileIds: [carolFile.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { + limit: 100, + withRenotes: false, + }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { + withRenotes: false, + }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { + withRenotes: false, + }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、フォローしているユーザーによるリノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、フォローしているユーザーによるリノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているリモートユーザーのノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: bob.id }, alice); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: bob.id }, alice); + + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const [bobFile, carolFile] = await Promise.all([ + uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), + uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), + ]); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [bobFile.id] }); + const carolNote1 = await post(carol, { text: 'hi' }); + const carolNote2 = await post(carol, { fileIds: [carolFile.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false); + }, 1000 * 30); + + test('フォローしているユーザーのチャンネル投稿が含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('自分の visibility: specified なノートが含まれる', async () => { + const [alice] = await Promise.all([signup()]); + + const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); + }); + + test('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); + }); + + test('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); + const aliceNote = await post(alice, { text: 'ok', visibility: 'specified', visibleUserIds: [bob.id], replyId: bobNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'ok'); + }); + + /* TODO + test('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] }); + const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id }); + await waitForPushToTl(); + const res = await api('notes/timeline', { limit: 100 }, alice); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id).text, 'ok'); + }); + */ + + // ↑の挙動が理想だけど実装が面倒かも + test('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] }); + const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + 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()]); + + await api('following/create', { + userId: alice.id, + }, bob); + + const aliceNote = await post(alice, { text: 'I\'m Alice.' }); + const bobNote = await post(bob, { text: 'I\'m Bob.' }); + const carolNote = await post(carol, { text: 'I\'m Carol.' }); + + await waitForPushToTl(); + + if (enableFanoutTimeline) { + // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる + assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1); + + const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1); + assert.strictEqual(bobHTL.includes(aliceNote.id), true); + assert.strictEqual(bobHTL.includes(bobNote.id), true); + assert.strictEqual(bobHTL.includes(carolNote.id), false); + } else { + assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0); + } + }); + + test('FTT: リモートユーザーの HTL にはプッシュされない', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + await api('following/create', { + userId: alice.id, + }, bob); + + await post(alice, { text: 'I\'m Alice.' }); + await post(bob, { text: 'I\'m Bob.' }); + + await waitForPushToTl(); + + // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる + assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0); + }); + + describe('凍結', () => { + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note; + + beforeAll(async () => { + [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + + await waitForPushToTl(); + + await api('admin/suspend-user', { userId: carol.id }, root); + await setTimeout(100); + }); + + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + const res = await api('notes/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); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await setTimeout(100); + + const res = await api('notes/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'); + }); + }); + + describe('凍結 (Renote)', () => { + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note, bobRenote: Note, carolRenote: Note; + + beforeAll(async () => { + [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + bobRenote = await post(bob, { renoteId: carolNote.id }); + carolRenote = await post(carol, { renoteId: bobNote.id }); + + await waitForPushToTl(); + + await api('admin/suspend-user', { userId: carol.id }, root); + await setTimeout(100); + }); + + test('凍結後に凍結されたユーザーに対するRenoteや凍結されたユーザーのRenoteが見えなくなる', async () => { + const res = await api('notes/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 => note.id === bobRenote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolRenote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーに対するRenoteや凍結されたユーザーのRenoteが見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await setTimeout(100); + + const res = await api('notes/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.some(note => note.id === bobRenote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolRenote.id), true); + }); + }); + + describe('凍結(リモート)', () => { + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note; + + beforeAll(async () => { + [alice, bob, carol] = await Promise.all([signup(), signup({ host: genHost() }), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + + await waitForPushToTl(); + + await api('admin/suspend-user', { userId: carol.id }, root); + await setTimeout(100); + }); + + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + const res = await api('notes/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); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await setTimeout(100); + + const res = await api('notes/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); + }); + }); + }); + + describe('Local TL', () => { + test('visibility: home なノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('他人の他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + }); + + test('他人のその人自身への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }); + + test('チャンネル投稿が含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('リモートユーザーのノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + // 含まれても良いと思うけど実装が面倒なので含まれない + test('フォローしているユーザーの visibility: home なノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('ミュートしているユーザーのノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); + }); + + test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); + }); + + test('[withReplies: true] 他人の他人への返信が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('[withFiles: true] ファイル付きノートのみ含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [file.id] }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); + 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/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); + + 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); + + 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); + }); + }); + }); + + describe('Social TL', () => { + test('ローカルユーザーのノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('ローカルユーザーの visibility: home なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-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); + }); + + test('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: carol.id }, bob); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.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); + assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); + }); + + test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; + + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + await api('following/create', { userId: carol.id }, bob); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.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); + assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); + assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id)?.text, 'hi'); + }); + + test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); + assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); + }); + + test('他人の他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + }); + + test('リモートユーザーのノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているリモートユーザーのノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: bob.id }, alice); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: bob.id }, alice); + + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ + if (!enableFanoutTimeline) return; + + const [alice, bob] = await Promise.all([signup(), signup()]); + + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); + }); + + test('[withReplies: true] 他人の他人への返信が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('[withFiles: true] ファイル付きノートのみ含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [file.id] }); + + await waitForPushToTl(); + + const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); + 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 = 未フォローのローカルユーザー (凍結対象でない) + * carol = 未フォローのローカルユーザー (凍結対象) + * dave = フォローしているローカルユーザー (凍結対象) + */ + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse, dave: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note, daveNote: Note; + + beforeAll(async () => { + [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('following/create', { userId: dave.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + daveNote = await post(dave, { text: 'hello' }); + + await waitForPushToTl(); + + await api('admin/suspend-user', { userId: carol.id }, root); + await api('admin/suspend-user', { userId: dave.id }, root); + await setTimeout(250); + }); + + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + const res = await api('notes/hybrid-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 => note.id === daveNote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await api('admin/unsuspend-user', { userId: dave.id }, root); + await setTimeout(250); + + const res = await api('notes/hybrid-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.some(note => note.id === daveNote.id), true); + }); + }); + + describe('凍結 (リモート)', () => { + /* + * carol = 未フォローのリモートユーザー (凍結対象) + * elle = フォローしているリモートユーザー (凍結対象) + */ + let alice: SignupResponse, carol: SignupResponse, elle: SignupResponse; + let aliceNote: Note, carolNote: Note, elleNote: Note; + + beforeAll(async () => { + [alice, carol, elle] = await Promise.all([signup(), signup({ host: genHost() }), signup({ host: genHost() })]); + + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); + await api('following/create', { userId: elle.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + elleNote = await post(elle, { text: 'hi there' }); + + await waitForPushToTl(); + + await api('admin/suspend-user', { userId: carol.id }, root); + await api('admin/suspend-user', { userId: elle.id }, root); + await setTimeout(250); + }); + + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === elleNote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await api('admin/unsuspend-user', { userId: elle.id }, root); + await setTimeout(250); + + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === elleNote.id), true); + }); + }); + }); + + describe('User List TL', () => { + test('リスインしているフォローしていないユーザーのノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }); + + test('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); + }); + + test('リスインしている自分の visibility: followers なノートが含まれる', async () => { + const [alice] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: alice.id }, alice); + await setTimeout(250); + const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); + }); + + test('リスインしているユーザーのチャンネルノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [file.id] }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }, 1000 * 10); + + test('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); + }); + + test('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/push', { listId: list.id, userId: carol.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + 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 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 }); + + 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); + + 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); + + 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); + }); + }); + }); + + describe('User TL', () => { + test('ノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); + }); + + test('自身の visibility: followers なノートが含まれる', async () => { + const [alice] = await Promise.all([signup()]); + + const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: alice.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); + }); + + test('チャンネル投稿が含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('[withReplies: false] 他人への返信が含まれない', async () => { + /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return; + + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false); + }); + + test('[withReplies: true] 他人への返信が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }); + + test('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false); + }); + + test('[withFiles: true] ファイル付きノートのみ含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [file.id] }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + }, 1000 * 10); + + test('[withChannelNotes: true] チャンネル投稿が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => { + const [bob] = await Promise.all([signup()]); + + const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); + const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + }); + + test('ミュートしているユーザーに関連する投稿が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => { + const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]); + + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); + await setTimeout(250); + const carolNote = await post(carol, { text: 'hi' }); + const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id }); + const bobNote = await post(bob, { renoteId: daveNote.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + test('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('mute/create', { userId: bob.id }, alice); + await setTimeout(250); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); + const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id }); + const bobNote4 = await post(bob, { renoteId: bobNote2.id }); + const bobNote5 = await post(bob, { renoteId: bobNote3.id }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote4.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote5.id), true); + }); + + test('自身の visibility: specified なノートが含まれる', async () => { + const [alice] = await Promise.all([signup()]); + + const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + }); + + test('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'specified' }); + + await waitForPushToTl(); + + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); + + assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); + }); + + /** @see https://github.com/misskey-dev/misskey/issues/14000 */ + test('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId による絞り込みが正しく動作する', async () => { + const alice = await signup(); + const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' }); + const note1 = await post(alice, { text: '1' }); + const note2 = await post(alice, { text: '2' }); + await redisForTimelines.del('list:userTimeline:' + alice.id); + const note3 = await post(alice, { text: '3' }); + + const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id }); + assert.deepStrictEqual(res.body, [note1, note2, note3]); + }); + + test('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId と untilId による絞り込みが正しく動作する', async () => { + const alice = await signup(); + const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' }); + const note1 = await post(alice, { text: '1' }); + const note2 = await post(alice, { text: '2' }); + await redisForTimelines.del('list:userTimeline:' + alice.id); + const note3 = await post(alice, { text: '3' }); + const noteUntil = await post(alice, { text: 'Note where id will be `untilId`.' }); + await post(alice, { text: '4' }); + + 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: ページネーションのテスト }); - - describe('Home TL', () => { - test.concurrent('自分の visibility: followers なノートが含まれる', async () => { - const [alice] = await Promise.all([signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); - }); - - test.concurrent('フォローしているユーザーのノートが含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi' }); - const carolNote = await post(carol, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); - const carolNote = await post(carol, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: carol.id }, bob); - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - await api('following/create', { userId: carol.id }, bob); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - 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, 'hi'); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: alice.id }, bob); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); - }); - - test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }); - - test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/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); - }); - - test.concurrent('自分の他人への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi' }); - const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - }); - - test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { - withRenotes: false, - }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { - withRenotes: false, - }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('following/create', { userId: bob.id }, alice); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('following/create', { userId: bob.id }, alice); - - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const [bobFile, carolFile] = await Promise.all([ - uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), - uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), - ]); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { fileIds: [bobFile.id] }); - const carolNote1 = await post(carol, { text: 'hi' }); - const carolNote2 = await post(carol, { fileIds: [carolFile.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false); - }, 1000 * 30); - - test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('自分の visibility: specified なノートが含まれる', async () => { - const [alice] = await Promise.all([signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); - }); - - test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); - }); - - test.concurrent('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); - const aliceNote = await post(alice, { text: 'ok', visibility: 'specified', visibleUserIds: [bob.id], replyId: bobNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'ok'); - }); - - /* TODO - test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] }); - const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id).text, 'ok'); - }); - */ - - // ↑の挙動が理想だけど実装が面倒かも - test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] }); - const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('FTT: ローカルユーザーの HTL にはプッシュされる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { - userId: alice.id, - }, bob); - - const aliceNote = await post(alice, { text: 'I\'m Alice.' }); - const bobNote = await post(bob, { text: 'I\'m Bob.' }); - const carolNote = await post(carol, { text: 'I\'m Carol.' }); - - await waitForPushToTl(); - - // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる - assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1); - - const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1); - assert.strictEqual(bobHTL.includes(aliceNote.id), true); - assert.strictEqual(bobHTL.includes(bobNote.id), true); - assert.strictEqual(bobHTL.includes(carolNote.id), false); - }); - - test.concurrent('FTT: リモートユーザーの HTL にはプッシュされない', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - await api('following/create', { - userId: alice.id, - }, bob); - - await post(alice, { text: 'I\'m Alice.' }); - await post(bob, { text: 'I\'m Bob.' }); - - await waitForPushToTl(); - - // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる - assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0); - }); - }); - - describe('Local TL', () => { - test.concurrent('visibility: home なノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('他人の他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); - }); - - test.concurrent('他人のその人自身への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }); - - test.concurrent('チャンネル投稿が含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('リモートユーザーのノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - // 含まれても良いと思うけど実装が面倒なので含まれない - test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('ミュートしているユーザーのノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); - }); - - test.concurrent('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); - }); - - test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { fileIds: [file.id] }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }, 1000 * 10); - }); - - describe('Social TL', () => { - test.concurrent('ローカルユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-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); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: carol.id }, bob); - await api('following/create', { userId: bob.id }, alice); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.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); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - await api('following/create', { userId: carol.id }, bob); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.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); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); - assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id)?.text, 'hi'); - }); - - test.concurrent('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: alice.id }, bob); - await api('following/update', { userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); - }); - - test.concurrent('他人の他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); - }); - - test.concurrent('リモートユーザーのノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/local-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('following/create', { userId: bob.id }, alice); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); - - await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('following/create', { userId: bob.id }, alice); - - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.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); - }); - - test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { fileIds: [file.id] }); - - await waitForPushToTl(); - - const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }, 1000 * 10); - }); - - describe('User List TL', () => { - test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }); - - test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); - }); - - test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => { - const [alice] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: alice.id }, alice); - await setTimeout(1000); - const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); - }); - - test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { fileIds: [file.id] }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }, 1000 * 10); - - test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); - }); - - test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('users/lists/push', { listId: list.id, userId: carol.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); - - await waitForPushToTl(); - - const res = await api('notes/user-list-timeline', { listId: list.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - }); - - describe('User TL', () => { - test.concurrent('ノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('following/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi'); - }); - - test.concurrent('自身の visibility: followers なノートが含まれる', async () => { - const [alice] = await Promise.all([signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: alice.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi'); - }); - - test.concurrent('チャンネル投稿が含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false); - }); - - test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }); - - test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - const carolNote = await post(carol, { text: 'hi' }); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false); - }); - - test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { fileIds: [file.id] }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - }, 1000 * 10); - - test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => { - const [bob] = await Promise.all([signup()]); - - const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); - const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - }); - - test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - - await api('mute/create', { userId: carol.id }, alice); - await setTimeout(1000); - const carolNote = await post(carol, { text: 'hi' }); - const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - await api('mute/create', { userId: bob.id }, alice); - await setTimeout(1000); - const bobNote1 = await post(bob, { text: 'hi' }); - const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); - const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true); - }); - - test.concurrent('自身の visibility: specified なノートが含まれる', async () => { - const [alice] = await Promise.all([signup()]); - - const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - }); - - test.concurrent('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup()]); - - const bobNote = await post(bob, { text: 'hi', visibility: 'specified' }); - - await waitForPushToTl(); - - const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); - - assert.strictEqual(res.body.some(note => note.id === bobNote.id), false); - }); - - /** @see https://github.com/misskey-dev/misskey/issues/14000 */ - test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId による絞り込みが正しく動作する', async () => { - const alice = await signup(); - const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' }); - const note1 = await post(alice, { text: '1' }); - const note2 = await post(alice, { text: '2' }); - await redisForTimelines.del('list:userTimeline:' + alice.id); - const note3 = await post(alice, { text: '3' }); - - const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id }); - assert.deepStrictEqual(res.body, [note1, note2, note3]); - }); - - test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId と untilId による絞り込みが正しく動作する', async () => { - const alice = await signup(); - const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' }); - const note1 = await post(alice, { text: '1' }); - const note2 = await post(alice, { text: '2' }); - await redisForTimelines.del('list:userTimeline:' + alice.id); - const note3 = await post(alice, { text: '3' }); - const noteUntil = await post(alice, { text: 'Note where id will be `untilId`.' }); - await post(alice, { text: '4' }); - - const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id, untilId: noteUntil.id }); - assert.deepStrictEqual(res.body, [note3, note2, note1]); - }); - }); - - // TODO: リノートミュート済みユーザーのテスト - // TODO: ページネーションのテスト }); diff --git a/packages/backend/test/e2e/well-known.ts b/packages/backend/test/e2e/well-known.ts index bdb298dfe4..538a990a4e 100644 --- a/packages/backend/test/e2e/well-known.ts +++ b/packages/backend/test/e2e/well-known.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { host, origin, relativeFetch, signup } from '../utils.js'; +import { api, host, origin, relativeFetch, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('.well-known', () => { @@ -14,6 +14,7 @@ describe('.well-known', () => { beforeAll(async () => { alice = await signup({ username: 'alice' }); + await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse); }, 1000 * 60 * 2); test('nodeinfo', async () => { diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts index 7c6dd6a55f..9185f58acb 100644 --- a/packages/backend/test/jest.setup.ts +++ b/packages/backend/test/jest.setup.ts @@ -9,3 +9,4 @@ beforeAll(async () => { await initTestDb(false); await sendEnvResetRequest(); }); + 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/resources/dummy-for-file-server-service.png b/packages/backend/test/resources/dummy-for-file-server-service.png new file mode 100644 index 0000000000..39332b0c1b Binary files /dev/null and b/packages/backend/test/resources/dummy-for-file-server-service.png differ diff --git a/packages/backend/test/resources/hw.png b/packages/backend/test/resources/hw.png new file mode 100644 index 0000000000..afe93faea6 Binary files /dev/null and b/packages/backend/test/resources/hw.png differ diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index 2b562acda8..a2a86c696e 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -23,7 +23,8 @@ "emitDecoratorMetadata": true, "resolveJsonModule": true, "isolatedModules": true, - "baseUrl": "./", + "jsx": "react-jsx", + "jsxImportSource": "@kitajs/html", "paths": { "@/*": ["../src/*"] }, diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts index 6d555326fb..9dad8e229d 100644 --- a/packages/backend/test/unit/AbuseReportNotificationService.ts +++ b/packages/backend/test/unit/AbuseReportNotificationService.ts @@ -162,10 +162,10 @@ describe('AbuseReportNotificationService', () => { emailService.sendEmail.mockClear(); webhookService.enqueueSystemWebhook.mockClear(); - await usersRepository.delete({}); - await userProfilesRepository.delete({}); - await systemWebhooksRepository.delete({}); - await abuseReportNotificationRecipientRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await userProfilesRepository.createQueryBuilder().delete().execute(); + await systemWebhooksRepository.createQueryBuilder().delete().execute(); + await abuseReportNotificationRecipientRepository.createQueryBuilder().delete().execute(); }); afterAll(async () => { diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index a79655c9aa..b3f7f426fe 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -26,7 +26,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; +import type { MockMetadata } from 'jest-mock'; const moduleMocker = new ModuleMocker(global); @@ -84,7 +84,7 @@ describe('AnnouncementService', () => { log: jest.fn(), }; } else if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const mockMetadata = moduleMocker.getMetadata(token) as MockMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); } @@ -103,10 +103,10 @@ describe('AnnouncementService', () => { afterEach(async () => { await Promise.all([ - app.get(DI.metasRepository).delete({}), - usersRepository.delete({}), - announcementsRepository.delete({}), - announcementReadsRepository.delete({}), + app.get(DI.metasRepository).createQueryBuilder().delete().execute(), + usersRepository.createQueryBuilder().delete().execute(), + announcementsRepository.createQueryBuilder().delete().execute(), + announcementReadsRepository.createQueryBuilder().delete().execute(), ]); await app.close(); diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts index e81a321c9b..93efa5d7d3 100644 --- a/packages/backend/test/unit/ApMfmService.ts +++ b/packages/backend/test/unit/ApMfmService.ts @@ -9,7 +9,6 @@ import { Test } from '@nestjs/testing'; import { CoreModule } from '@/core/CoreModule.js'; import { ApMfmService } from '@/core/activitypub/ApMfmService.js'; import { GlobalModule } from '@/GlobalModule.js'; -import { MiNote } from '@/models/Note.js'; describe('ApMfmService', () => { let apMfmService: ApMfmService; @@ -31,7 +30,7 @@ describe('ApMfmService', () => { const { content, noMisskeyContent } = apMfmService.getNoteHtml(note); assert.equal(noMisskeyContent, true, 'noMisskeyContent'); - assert.equal(content, '

テキスト @mention 🍊 ​:emoji:​ https://example.com

', 'content'); + assert.equal(content, 'テキスト @mention 🍊 ​:emoji:​ https://example.com', 'content'); }); test('Provide _misskey_content for MFM', () => { @@ -43,7 +42,7 @@ describe('ApMfmService', () => { const { content, noMisskeyContent } = apMfmService.getNoteHtml(note); assert.equal(noMisskeyContent, false, 'noMisskeyContent'); - assert.equal(content, '

foo

', 'content'); + assert.equal(content, 'foo', 'content'); }); }); }); diff --git a/packages/backend/test/unit/CaptchaService.ts b/packages/backend/test/unit/CaptchaService.ts index 51b70b05a1..24bb81118e 100644 --- a/packages/backend/test/unit/CaptchaService.ts +++ b/packages/backend/test/unit/CaptchaService.ts @@ -446,7 +446,7 @@ describe('CaptchaService', () => { if (!res.success) { expect(res.error.code).toBe(code); } - expect(metaService.update).not.toBeCalled(); + expect(metaService.update).not.toHaveBeenCalled(); } describe('invalidParameters', () => { 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/CustomEmojiService.ts b/packages/backend/test/unit/CustomEmojiService.ts index 10b687c6a0..d6c73a2091 100644 --- a/packages/backend/test/unit/CustomEmojiService.ts +++ b/packages/backend/test/unit/CustomEmojiService.ts @@ -86,7 +86,7 @@ describe('CustomEmojiService', () => { } afterEach(async () => { - await emojisRepository.delete({}); + await emojisRepository.createQueryBuilder().delete().execute(); }); describe('単独', () => { diff --git a/packages/backend/test/unit/DriveService.ts b/packages/backend/test/unit/DriveService.ts index 964c65ccaa..48b108fbba 100644 --- a/packages/backend/test/unit/DriveService.ts +++ b/packages/backend/test/unit/DriveService.ts @@ -53,7 +53,7 @@ describe('DriveService', () => { s3Mock.on(DeleteObjectCommand) .rejects(new InvalidObjectState({ $metadata: {}, message: '' })); - await expect(driveService.deleteObjectStorageFile('unexpected')).rejects.toThrowError(Error); + await expect(driveService.deleteObjectStorageFile('unexpected')).rejects.toThrow(Error); }); test('delete a file with no valid key', async () => { diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts index 29bd03a201..28a2a971f4 100644 --- a/packages/backend/test/unit/FileInfoService.ts +++ b/packages/backend/test/unit/FileInfoService.ts @@ -17,7 +17,7 @@ import { FileInfo, FileInfoService } from '@/core/FileInfoService.js'; import { AiService } from '@/core/AiService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; +import type { MockMetadata } from 'jest-mock'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -34,7 +34,7 @@ describe('FileInfoService', () => { delete fi.sensitive; delete fi.blurhash; delete fi.porn; - + return fi; } @@ -54,7 +54,7 @@ describe('FileInfoService', () => { // return { }; //} if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const mockMetadata = moduleMocker.getMetadata(token) as MockMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); } diff --git a/packages/backend/test/unit/FlashService.ts b/packages/backend/test/unit/FlashService.ts index f2d9832f50..91c2286ff6 100644 --- a/packages/backend/test/unit/FlashService.ts +++ b/packages/backend/test/unit/FlashService.ts @@ -7,9 +7,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { FlashService } from '@/core/FlashService.js'; import { IdService } from '@/core/IdService.js'; -import { FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { FlashLikesRepository, FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; describe('FlashService', () => { let app: TestingModule; @@ -18,6 +19,7 @@ describe('FlashService', () => { // -------------------------------------------------------------------------------------- let flashsRepository: FlashsRepository; + let flashLikesRepository: FlashLikesRepository; let usersRepository: UsersRepository; let userProfilesRepository: UserProfilesRepository; let idService: IdService; @@ -65,6 +67,7 @@ describe('FlashService', () => { app = await Test.createTestingModule({ imports: [ GlobalModule, + CoreModule, ], providers: [ FlashService, @@ -75,6 +78,7 @@ describe('FlashService', () => { service = app.get(FlashService); flashsRepository = app.get(DI.flashsRepository); + flashLikesRepository = app.get(DI.flashLikesRepository); usersRepository = app.get(DI.usersRepository); userProfilesRepository = app.get(DI.userProfilesRepository); idService = app.get(IdService); @@ -85,9 +89,10 @@ describe('FlashService', () => { }); afterEach(async () => { - await usersRepository.delete({}); - await userProfilesRepository.delete({}); - await flashsRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await userProfilesRepository.createQueryBuilder().delete().execute(); + await flashsRepository.createQueryBuilder().delete().execute(); + await flashLikesRepository.createQueryBuilder().delete().execute(); }); afterAll(async () => { diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts index 7350da3cae..2f5f3745de 100644 --- a/packages/backend/test/unit/MfmService.ts +++ b/packages/backend/test/unit/MfmService.ts @@ -24,25 +24,25 @@ describe('MfmService', () => { describe('toHtml', () => { test('br', () => { const input = 'foo\nbar\nbaz'; - const output = '

foo
bar
baz

'; + const output = 'foo
bar
baz'; assert.equal(mfmService.toHtml(mfm.parse(input)), output); }); test('br alt', () => { const input = 'foo\r\nbar\rbaz'; - const output = '

foo
bar
baz

'; + const output = 'foo
bar
baz'; assert.equal(mfmService.toHtml(mfm.parse(input)), output); }); test('Do not generate unnecessary span', () => { const input = 'foo $[tada bar]'; - const output = '

foo bar

'; + const output = 'foo bar'; assert.equal(mfmService.toHtml(mfm.parse(input)), output); }); test('escape', () => { const input = '```\n

Hello, world!

\n```'; - const output = '

<p>Hello, world!</p>

'; + const output = '
<p>Hello, world!</p>
'; assert.equal(mfmService.toHtml(mfm.parse(input)), output); }); }); @@ -118,7 +118,7 @@ describe('MfmService', () => { assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミス キー) b c

'), 'a Misskey(ミス キー) b c'); assert.deepStrictEqual( mfmService.fromHtml('

a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b

'), - 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b' + 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b', ); }); diff --git a/packages/backend/test/unit/NoteCreateService.ts b/packages/backend/test/unit/NoteCreateService.ts index f2d4c8ffbb..f3d3d1da99 100644 --- a/packages/backend/test/unit/NoteCreateService.ts +++ b/packages/backend/test/unit/NoteCreateService.ts @@ -40,6 +40,7 @@ describe('NoteCreateService', () => { renoteCount: 0, repliesCount: 0, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, @@ -60,6 +61,7 @@ describe('NoteCreateService', () => { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, }; const poll: IPoll = { diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts index 1957f4544c..3cfb4ff3f8 100644 --- a/packages/backend/test/unit/ReactionService.ts +++ b/packages/backend/test/unit/ReactionService.ts @@ -21,73 +21,73 @@ describe('ReactionService', () => { }); describe('normalize', () => { - test('絵文字リアクションはそのまま', async () => { - assert.strictEqual(await reactionService.normalize('👍'), '👍'); - assert.strictEqual(await reactionService.normalize('🍅'), '🍅'); + test('絵文字リアクションはそのまま', () => { + assert.strictEqual(reactionService.normalize('👍'), '👍'); + assert.strictEqual(reactionService.normalize('🍅'), '🍅'); }); - test('既存のリアクションは絵文字化する pudding', async () => { - assert.strictEqual(await reactionService.normalize('pudding'), '🍮'); + test('既存のリアクションは絵文字化する pudding', () => { + assert.strictEqual(reactionService.normalize('pudding'), '🍮'); }); - test('既存のリアクションは絵文字化する like', async () => { - assert.strictEqual(await reactionService.normalize('like'), '👍'); + test('既存のリアクションは絵文字化する like', () => { + assert.strictEqual(reactionService.normalize('like'), '👍'); }); - test('既存のリアクションは絵文字化する love', async () => { - assert.strictEqual(await reactionService.normalize('love'), '❤'); + test('既存のリアクションは絵文字化する love', () => { + assert.strictEqual(reactionService.normalize('love'), '❤'); }); - test('既存のリアクションは絵文字化する laugh', async () => { - assert.strictEqual(await reactionService.normalize('laugh'), '😆'); + test('既存のリアクションは絵文字化する laugh', () => { + assert.strictEqual(reactionService.normalize('laugh'), '😆'); }); - test('既存のリアクションは絵文字化する hmm', async () => { - assert.strictEqual(await reactionService.normalize('hmm'), '🤔'); + test('既存のリアクションは絵文字化する hmm', () => { + assert.strictEqual(reactionService.normalize('hmm'), '🤔'); }); - test('既存のリアクションは絵文字化する surprise', async () => { - assert.strictEqual(await reactionService.normalize('surprise'), '😮'); + test('既存のリアクションは絵文字化する surprise', () => { + assert.strictEqual(reactionService.normalize('surprise'), '😮'); }); - test('既存のリアクションは絵文字化する congrats', async () => { - assert.strictEqual(await reactionService.normalize('congrats'), '🎉'); + test('既存のリアクションは絵文字化する congrats', () => { + assert.strictEqual(reactionService.normalize('congrats'), '🎉'); }); - test('既存のリアクションは絵文字化する angry', async () => { - assert.strictEqual(await reactionService.normalize('angry'), '💢'); + test('既存のリアクションは絵文字化する angry', () => { + assert.strictEqual(reactionService.normalize('angry'), '💢'); }); - test('既存のリアクションは絵文字化する confused', async () => { - assert.strictEqual(await reactionService.normalize('confused'), '😥'); + test('既存のリアクションは絵文字化する confused', () => { + assert.strictEqual(reactionService.normalize('confused'), '😥'); }); - test('既存のリアクションは絵文字化する rip', async () => { - assert.strictEqual(await reactionService.normalize('rip'), '😇'); + test('既存のリアクションは絵文字化する rip', () => { + assert.strictEqual(reactionService.normalize('rip'), '😇'); }); - test('既存のリアクションは絵文字化する star', async () => { - assert.strictEqual(await reactionService.normalize('star'), '⭐'); + test('既存のリアクションは絵文字化する star', () => { + assert.strictEqual(reactionService.normalize('star'), '⭐'); }); - test('異体字セレクタ除去', async () => { - assert.strictEqual(await reactionService.normalize('㊗️'), '㊗'); + test('異体字セレクタ除去', () => { + assert.strictEqual(reactionService.normalize('㊗️'), '㊗'); }); - test('異体字セレクタ除去 必要なし', async () => { - assert.strictEqual(await reactionService.normalize('㊗'), '㊗'); + test('異体字セレクタ除去 必要なし', () => { + assert.strictEqual(reactionService.normalize('㊗'), '㊗'); }); - test('fallback - null', async () => { - assert.strictEqual(await reactionService.normalize(null), '❤'); + test('fallback - null', () => { + assert.strictEqual(reactionService.normalize(null), '❤'); }); - test('fallback - empty', async () => { - assert.strictEqual(await reactionService.normalize(''), '❤'); + test('fallback - empty', () => { + assert.strictEqual(reactionService.normalize(''), '❤'); }); - test('fallback - unknown', async () => { - assert.strictEqual(await reactionService.normalize('unknown'), '❤'); + test('fallback - unknown', () => { + assert.strictEqual(reactionService.normalize('unknown'), '❤'); }); }); diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index 074430dd31..bee580d0c7 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -9,7 +9,7 @@ import { jest } from '@jest/globals'; import { Test } from '@nestjs/testing'; import { ModuleMocker } from 'jest-mock'; import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; +import type { MockMetadata } from 'jest-mock'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { IdService } from '@/core/IdService.js'; @@ -45,7 +45,7 @@ describe('RelayService', () => { return { deliver: jest.fn() }; } if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const mockMetadata = moduleMocker.getMetadata(token) as MockMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); } diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 553ff0982a..9b17b1fbb9 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -6,12 +6,12 @@ process.env.NODE_ENV = 'test'; import { setTimeout } from 'node:timers/promises'; -import { jest } from '@jest/globals'; +import { describe, jest } from '@jest/globals'; import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import * as lolex from '@sinonjs/fake-timers'; import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; +import type { MockMetadata } from 'jest-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; import { @@ -104,6 +104,8 @@ describe('RoleService', () => { beforeEach(async () => { clock = lolex.install({ + // https://github.com/sinonjs/sinon/issues/2620 + toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[], now: new Date(), shouldClearNativeTimers: true, }); @@ -135,7 +137,7 @@ describe('RoleService', () => { return { fetch: jest.fn() }; } if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const mockMetadata = moduleMocker.getMetadata(token) as MockMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); } @@ -158,16 +160,75 @@ describe('RoleService', () => { afterEach(async () => { clock.uninstall(); + /** + * Delete meta and roleAssignment first to avoid deadlock due to schema dependencies + * https://github.com/misskey-dev/misskey/issues/16783 + */ + await app.get(DI.metasRepository).createQueryBuilder().delete().execute(); + await roleAssignmentsRepository.createQueryBuilder().delete().execute(); await Promise.all([ - app.get(DI.metasRepository).delete({}), - usersRepository.delete({}), - rolesRepository.delete({}), - roleAssignmentsRepository.delete({}), + usersRepository.createQueryBuilder().delete().execute(), + rolesRepository.createQueryBuilder().delete().execute(), ]); await app.close(); }); + describe('getUserAssigns', () => { + test('アサインされたロールを取得できる', async () => { + const user = await createUser(); + const role1 = await createRole({ name: 'a' }); + const role2 = await createRole({ name: 'b' }); + + await roleService.assign(user.id, role1.id); + await roleService.assign(user.id, role2.id); + + const assigns = await roleService.getUserAssigns(user.id); + expect(assigns).toHaveLength(2); + expect(assigns.some(a => a.roleId === role1.id)).toBe(true); + expect(assigns.some(a => a.roleId === role2.id)).toBe(true); + }); + + test('アサインされたロールの有効/期限切れパターンを取得できる', async () => { + const user = await createUser(); + const roleNoExpiry = await createRole({ name: 'no-expires' }); + const roleNotExpired = await createRole({ name: 'not-expired' }); + const roleExpired = await createRole({ name: 'expired' }); + + // expiresAtなし + await roleService.assign(user.id, roleNoExpiry.id); + + // expiresAtあり(期限切れでない) + const future = new Date(Date.now() + 1000 * 60 * 60); // +1 hour + await roleService.assign(user.id, roleNotExpired.id, future); + + // expiresAtあり(期限切れ) + await assignRole({ userId: user.id, roleId: roleExpired.id, expiresAt: new Date(Date.now() - 1000) }); + + const assigns = await roleService.getUserAssigns(user.id); + expect(assigns.some(a => a.roleId === roleNoExpiry.id)).toBe(true); + expect(assigns.some(a => a.roleId === roleNotExpired.id)).toBe(true); + expect(assigns.some(a => a.roleId === roleExpired.id)).toBe(false); + }); + }); + + describe('getUserRoles', () => { + test('アサインされたロールとコンディショナルロールの両方が取得できる', async () => { + const user = await createUser(); + const manualRole = await createRole({ name: 'manual role' }); + const conditionalRole = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + await roleService.assign(user.id, manualRole.id); + await roleService.assign(user.id, conditionalRole.id); + + const roles = await roleService.getUserRoles(user.id); + expect(roles.some(r => r.id === manualRole.id)).toBe(true); + expect(roles.some(r => r.id === conditionalRole.id)).toBe(true); + }); + }); + describe('getUserPolicies', () => { test('instance default policies', async () => { const user = await createUser(); @@ -280,6 +341,112 @@ describe('RoleService', () => { const resultAfter25hAgain = await roleService.getUserPolicies(user.id); expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); }); + + test('role with no policy set', async () => { + const user = await createUser(); + const roleWithPolicy = await createRole({ + name: 'roleWithPolicy', + policies: { + pinLimit: { + useDefault: false, + priority: 0, + value: 10, + }, + }, + }); + const roleWithoutPolicy = await createRole({ + name: 'roleWithoutPolicy', + policies: {}, // ポリシーが空 + }); + await roleService.assign(user.id, roleWithPolicy.id); + await roleService.assign(user.id, roleWithoutPolicy.id); + meta.policies = { + pinLimit: 5, + }; + + const result = await roleService.getUserPolicies(user.id); + + // roleWithoutPolicy は default 値 (5) を使い、roleWithPolicy の 10 と比較して大きい方が採用される + expect(result.pinLimit).toBe(10); + }); + }); + + describe('getUserBadgeRoles', () => { + test('手動アサイン済みのバッジロールのみが返る', async () => { + const user = await createUser(); + const badgeRole = await createRole({ name: 'badge', asBadge: true }); + const normalRole = await createRole({ name: 'normal', asBadge: false }); + + await roleService.assign(user.id, badgeRole.id); + await roleService.assign(user.id, normalRole.id); + + const roles = await roleService.getUserBadgeRoles(user.id); + expect(roles.some(r => r.id === badgeRole.id)).toBe(true); + expect(roles.some(r => r.id === normalRole.id)).toBe(false); + }); + + test('コンディショナルなバッジロールが条件一致で返る', async () => { + const user = await createUser({ isBot: true }); + const condBadgeRole = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }, { asBadge: true, name: 'cond-badge' }); + const condNonBadgeRole = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }, { asBadge: false, name: 'cond-non-badge' }); + + const roles = await roleService.getUserBadgeRoles(user.id); + expect(roles.some(r => r.id === condBadgeRole.id)).toBe(true); + expect(roles.some(r => r.id === condNonBadgeRole.id)).toBe(false); + }); + + test('roleAssignedTo 条件のバッジロール: アサイン有無で変化する', async () => { + const [user1, user2] = await Promise.all([createUser(), createUser()]); + const manualRole = await createRole({ name: 'manual' }); + const condBadgeRole = await createConditionalRole({ + id: aidx(), + type: 'roleAssignedTo', + roleId: manualRole.id, + }, { asBadge: true, name: 'assigned-badge' }); + + await roleService.assign(user2.id, manualRole.id); + + const [roles1, roles2] = await Promise.all([ + roleService.getUserBadgeRoles(user1.id), + roleService.getUserBadgeRoles(user2.id), + ]); + expect(roles1.some(r => r.id === condBadgeRole.id)).toBe(false); + expect(roles2.some(r => r.id === condBadgeRole.id)).toBe(true); + }); + + test('期限切れのバッジロールは除外される', async () => { + const user = await createUser(); + const roleNoExpiry = await createRole({ name: 'no-exp', asBadge: true }); + const roleNotExpired = await createRole({ name: 'not-expired', asBadge: true }); + const roleExpired = await createRole({ name: 'expired', asBadge: true }); + + // expiresAt なし + await roleService.assign(user.id, roleNoExpiry.id); + + // expiresAt あり(期限切れでない) + const future = new Date(Date.now() + 1000 * 60 * 60); // +1 hour + await roleService.assign(user.id, roleNotExpired.id, future); + + // expiresAt あり(期限切れ) + await assignRole({ userId: user.id, roleId: roleExpired.id, expiresAt: new Date(Date.now() - 1000) }); + + const rolesBefore = await roleService.getUserBadgeRoles(user.id); + expect(rolesBefore.some(r => r.id === roleNoExpiry.id)).toBe(true); + expect(rolesBefore.some(r => r.id === roleNotExpired.id)).toBe(true); + expect(rolesBefore.some(r => r.id === roleExpired.id)).toBe(false); + + // 時間経過で roleNotExpired を失効させる + clock.tick('02:00:00'); + const rolesAfter = await roleService.getUserBadgeRoles(user.id); + expect(rolesAfter.some(r => r.id === roleNoExpiry.id)).toBe(true); + expect(rolesAfter.some(r => r.id === roleNotExpired.id)).toBe(false); + }); }); describe('getModeratorIds', () => { @@ -413,9 +580,9 @@ describe('RoleService', () => { expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]); }); - test('root has moderator role', async () => { - const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ - createUser(), createUser(), createUser(), createRoot(), + test('includeAdmins = false, includeRoot = true, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -424,9 +591,11 @@ describe('RoleService', () => { await Promise.all([ assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: modeUser1.id, roleId: role2.id }), - assignRole({ userId: rootUser.id, roleId: role2.id }), + assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser1.id, roleId: role3.id }), + assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); const result = await roleService.getModeratorIds({ @@ -434,12 +603,12 @@ describe('RoleService', () => { includeRoot: true, excludeExpire: false, }); - expect(result).toEqual([modeUser1.id, rootUser.id]); + expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]); }); - test('root has administrator role', async () => { - const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ - createUser(), createUser(), createUser(), createRoot(), + test('includeAdmins = true, includeRoot = true, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -448,9 +617,11 @@ describe('RoleService', () => { await Promise.all([ assignRole({ userId: adminUser1.id, roleId: role1.id }), - assignRole({ userId: rootUser.id, roleId: role1.id }), + assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser1.id, roleId: role3.id }), + assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); const result = await roleService.getModeratorIds({ @@ -458,12 +629,12 @@ describe('RoleService', () => { includeRoot: true, excludeExpire: false, }); - expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]); + expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id, rootUser.id]); }); - test('root has moderator role(expire)', async () => { - const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ - createUser(), createUser(), createUser(), createRoot(), + test('includeAdmins = true, includeRoot = true, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -472,17 +643,71 @@ describe('RoleService', () => { await Promise.all([ assignRole({ userId: adminUser1.id, roleId: role1.id }), - assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), - assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser1.id, roleId: role3.id }), + assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); const result = await roleService.getModeratorIds({ - includeAdmins: false, + includeAdmins: true, includeRoot: true, excludeExpire: true, }); - expect(result).toEqual([rootUser.id]); + expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]); + }); + }); + + describe('getAdministratorIds', () => { + test('should return only user IDs with administrator roles', async () => { + const adminUser1 = await createUser(); + const adminUser2 = await createUser(); + const normalUser = await createUser(); + const moderatorUser = await createUser(); + + const adminRole = await createRole({ name: 'admin', isAdministrator: true, isModerator: false }); + const moderatorRole = await createRole({ name: 'moderator', isModerator: true, isAdministrator: false }); + const normalRole = await createRole({ name: 'normal', isAdministrator: false, isModerator: false }); + + await roleService.assign(adminUser1.id, adminRole.id); + await roleService.assign(adminUser2.id, adminRole.id); + await roleService.assign(moderatorUser.id, moderatorRole.id); + await roleService.assign(normalUser.id, normalRole.id); + + const adminIds = await roleService.getAdministratorIds(); + + // sort for deterministic order + adminIds.sort(); + const expectedIds = [adminUser1.id, adminUser2.id].sort(); + + expect(adminIds).toEqual(expectedIds); + }); + + test('should return an empty array if no users have administrator roles', async () => { + const normalUser = await createUser(); + const normalRole = await createRole({ name: 'normal', isAdministrator: false }); + await roleService.assign(normalUser.id, normalRole.id); + + const adminIds = await roleService.getAdministratorIds(); + + expect(adminIds).toHaveLength(0); + }); + + test('should return an empty array if there are no administrator roles defined', async () => { + await createUser(); // create user to ensure not empty db + const adminIds = await roleService.getAdministratorIds(); + expect(adminIds).toHaveLength(0); + }); + + // TODO: rootユーザーは現在実装に含まれていないため、テストもそれに倣う + test('should not include the root user', async () => { + const rootUser = await createUser(); + meta.rootUserId = rootUser.id; + + const adminIds = await roleService.getAdministratorIds(); + + expect(adminIds).not.toContain(rootUser.id); }); }); diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts index 151f3b826a..6e7e5a8b59 100644 --- a/packages/backend/test/unit/S3Service.ts +++ b/packages/backend/test/unit/S3Service.ts @@ -72,7 +72,7 @@ describe('S3Service', () => { Bucket: 'fake', Key: 'fake', Body: 'x', - })).rejects.toThrowError(Error); + })).rejects.toThrow(Error); }); test('upload a large file error', async () => { @@ -82,7 +82,7 @@ describe('S3Service', () => { Bucket: 'fake', Key: 'fake', Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ - })).rejects.toThrowError(Error); + })).rejects.toThrow(Error); }); }); }); diff --git a/packages/backend/test/unit/SearchService.ts b/packages/backend/test/unit/SearchService.ts new file mode 100644 index 0000000000..6e17bef1c3 --- /dev/null +++ b/packages/backend/test/unit/SearchService.ts @@ -0,0 +1,483 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { afterAll, afterEach, beforeAll, describe, expect, test } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import type { Index, MeiliSearch } from 'meilisearch'; +import { type Config, loadConfig } from '@/config.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { SearchService } from '@/core/SearchService.js'; +import { CacheService } from '@/core/CacheService.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { + type BlockingsRepository, + type ChannelsRepository, + type FollowingsRepository, + type MutingsRepository, + type NotesRepository, + type UserProfilesRepository, + type UsersRepository, + type MiChannel, + type MiNote, + type MiUser, +} from '@/models/_.js'; + +describe('SearchService', () => { + type TestContext = { + app: TestingModule; + service: SearchService; + cacheService: CacheService; + idService: IdService; + mutingsRepository: MutingsRepository; + blockingsRepository: BlockingsRepository; + usersRepository: UsersRepository; + userProfilesRepository: UserProfilesRepository; + notesRepository: NotesRepository; + channelsRepository: ChannelsRepository; + followingsRepository: FollowingsRepository; + indexer?: (note: MiNote) => Promise; + }; + + const meilisearchSettings = { + searchableAttributes: [ + 'text', + 'cw', + ], + sortableAttributes: [ + 'createdAt', + ], + filterableAttributes: [ + 'createdAt', + 'userId', + 'userHost', + 'channelId', + 'tags', + ], + typoTolerance: { + enabled: false, + }, + pagination: { + maxTotalHits: 10000, + }, + }; + + async function buildContext(configOverride?: Config): Promise { + const builder = Test.createTestingModule({ + imports: [ + GlobalModule, + CoreModule, + ], + }); + + if (configOverride) { + builder.overrideProvider(DI.config).useValue(configOverride); + } + + const app = await builder.compile(); + + app.enableShutdownHooks(); + + return { + app, + service: app.get(SearchService), + cacheService: app.get(CacheService), + idService: app.get(IdService), + mutingsRepository: app.get(DI.mutingsRepository), + blockingsRepository: app.get(DI.blockingsRepository), + usersRepository: app.get(DI.usersRepository), + userProfilesRepository: app.get(DI.userProfilesRepository), + notesRepository: app.get(DI.notesRepository), + channelsRepository: app.get(DI.channelsRepository), + followingsRepository: app.get(DI.followingsRepository), + }; + } + + async function cleanupContext(ctx: TestContext) { + await ctx.notesRepository.createQueryBuilder().delete().execute(); + await ctx.mutingsRepository.createQueryBuilder().delete().execute(); + await ctx.blockingsRepository.createQueryBuilder().delete().execute(); + await ctx.followingsRepository.createQueryBuilder().delete().execute(); + await ctx.channelsRepository.createQueryBuilder().delete().execute(); + await ctx.userProfilesRepository.createQueryBuilder().delete().execute(); + await ctx.usersRepository.createQueryBuilder().delete().execute(); + } + + async function createUser(ctx: TestContext, data: Partial = {}) { + const id = ctx.idService.gen(); + const username = data.username ?? `user_${id}`; + const usernameLower = data.usernameLower ?? username.toLowerCase(); + + const user = await ctx.usersRepository + .insert({ + id, + username, + usernameLower, + ...data, + }) + .then(x => ctx.usersRepository.findOneByOrFail(x.identifiers[0])); + + await ctx.userProfilesRepository.insert({ + userId: id, + }); + + return user; + } + + async function createChannel(ctx: TestContext, user: MiUser, data: Partial = {}) { + const id = ctx.idService.gen(); + const channel = await ctx.channelsRepository + .insert({ + id, + userId: user.id, + name: data.name ?? `channel_${id}`, + ...data, + }) + .then(x => ctx.channelsRepository.findOneByOrFail(x.identifiers[0])); + + return channel; + } + + async function createNote(ctx: TestContext, user: MiUser, data: Partial = {}, time?: number) { + const id = time == null ? ctx.idService.gen() : ctx.idService.gen(time); + const note = await ctx.notesRepository + .insert({ + id, + text: 'hello', + userId: user.id, + userHost: user.host, + visibility: 'public', + tags: [], + ...data, + }) + .then(x => ctx.notesRepository.findOneByOrFail(x.identifiers[0])); + + if (ctx.indexer) { + await ctx.indexer(note); + } + + return note; + } + + async function createFollowing(ctx: TestContext, follower: MiUser, followee: MiUser) { + await ctx.followingsRepository.insert({ + id: ctx.idService.gen(), + followerId: follower.id, + followeeId: followee.id, + followerHost: follower.host, + followeeHost: followee.host, + }); + } + + function clearUserCaches(ctx: TestContext, userId: MiUser['id']) { + ctx.cacheService.userMutingsCache.delete(userId); + ctx.cacheService.userBlockedCache.delete(userId); + ctx.cacheService.userBlockingCache.delete(userId); + } + + async function createMuting(ctx: TestContext, muter: MiUser, mutee: MiUser) { + await ctx.mutingsRepository.insert({ + id: ctx.idService.gen(), + muterId: muter.id, + muteeId: mutee.id, + }); + clearUserCaches(ctx, muter.id); + } + + async function createBlocking(ctx: TestContext, blocker: MiUser, blockee: MiUser) { + await ctx.blockingsRepository.insert({ + id: ctx.idService.gen(), + blockerId: blocker.id, + blockeeId: blockee.id, + }); + clearUserCaches(ctx, blocker.id); + clearUserCaches(ctx, blockee.id); + } + + function defineSearchNoteTests( + getCtx: () => TestContext, + { + supportsFollowersVisibility, + sinceIdOrder, + }: { + supportsFollowersVisibility: boolean; + sinceIdOrder: 'asc' | 'desc'; + }, + ) { + describe('searchNote', () => { + test('filters notes by visibility (followers only visible to followers)', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const author = await createUser(ctx, { username: 'author', usernameLower: 'author', host: null }); + + const publicNote = await createNote(ctx, author, { text: 'hello public', visibility: 'public' }); + const followersNote = await createNote(ctx, author, { text: 'hello followers', visibility: 'followers' }); + + const beforeFollow = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + expect(beforeFollow.map(note => note.id)).toEqual([publicNote.id]); + + await createFollowing(ctx, me, author); + + const afterFollow = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + const expectedIds = supportsFollowersVisibility + ? [followersNote.id, publicNote.id] + : [publicNote.id]; + expect(afterFollow.map(note => note.id).sort()).toEqual(expectedIds.sort()); + }); + + test('filters out suspended users via base note filtering', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const active = await createUser(ctx, { username: 'active', usernameLower: 'active', host: null }); + const suspended = await createUser(ctx, { username: 'suspended', usernameLower: 'suspended', host: null, isSuspended: true }); + + const activeNote = await createNote(ctx, active, { text: 'hello active', visibility: 'public' }); + await createNote(ctx, suspended, { text: 'hello suspended', visibility: 'public' }); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + expect(result.map(note => note.id)).toEqual([activeNote.id]); + }); + + test('filters by userId', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const alice = await createUser(ctx, { username: 'alice', usernameLower: 'alice', host: null }); + const bob = await createUser(ctx, { username: 'bob', usernameLower: 'bob', host: null }); + + const aliceNote = await createNote(ctx, alice, { text: 'hello alice', visibility: 'public' }); + await createNote(ctx, bob, { text: 'hello bob', visibility: 'public' }); + + const result = await ctx.service.searchNote('hello', me, { userId: alice.id }, { limit: 10 }); + expect(result.map(note => note.id)).toEqual([aliceNote.id]); + }); + + test('filters by channelId', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const author = await createUser(ctx, { username: 'author', usernameLower: 'author', host: null }); + const channelA = await createChannel(ctx, author, { name: 'channel-a' }); + const channelB = await createChannel(ctx, author, { name: 'channel-b' }); + + const channelNote = await createNote(ctx, author, { text: 'hello channel', channelId: channelA.id, visibility: 'public' }); + await createNote(ctx, author, { text: 'hello other', channelId: channelB.id, visibility: 'public' }); + + const result = await ctx.service.searchNote('hello', me, { channelId: channelA.id }, { limit: 10 }); + expect(result.map(note => note.id)).toEqual([channelNote.id]); + }); + + test('filters by host', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const local = await createUser(ctx, { username: 'local', usernameLower: 'local', host: null }); + const remote = await createUser(ctx, { username: 'remote', usernameLower: 'remote', host: 'example.com' }); + + const localNote = await createNote(ctx, local, { text: 'hello local', visibility: 'public' }); + const remoteNote = await createNote(ctx, remote, { text: 'hello remote', visibility: 'public', userHost: 'example.com' }); + + const localResult = await ctx.service.searchNote('hello', me, { host: '.' }, { limit: 10 }); + expect(localResult.map(note => note.id)).toEqual([localNote.id]); + + const remoteResult = await ctx.service.searchNote('hello', me, { host: 'example.com' }, { limit: 10 }); + expect(remoteResult.map(note => note.id)).toEqual([remoteNote.id]); + }); + + describe('muting and blocking', () => { + test('filters out muted users', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const muted = await createUser(ctx, { username: 'muted', usernameLower: 'muted', host: null }); + const other = await createUser(ctx, { username: 'other', usernameLower: 'other', host: null }); + + await createNote(ctx, muted, { text: 'hello muted', visibility: 'public' }); + const otherNote = await createNote(ctx, other, { text: 'hello other', visibility: 'public' }); + + await createMuting(ctx, me, muted); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + + expect(result.map(note => note.id)).toEqual([otherNote.id]); + }); + + test('filters out users who block me', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const blocker = await createUser(ctx, { username: 'blocker', usernameLower: 'blocker', host: null }); + const other = await createUser(ctx, { username: 'other', usernameLower: 'other', host: null }); + + await createNote(ctx, blocker, { text: 'hello blocker', visibility: 'public' }); + const otherNote = await createNote(ctx, other, { text: 'hello other', visibility: 'public' }); + + await createBlocking(ctx, blocker, me); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + + expect(result.map(note => note.id)).toEqual([otherNote.id]); + }); + + test('filters no out users I block', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const blocked = await createUser(ctx, { username: 'blocked', usernameLower: 'blocked', host: null }); + const other = await createUser(ctx, { username: 'other', usernameLower: 'other', host: null }); + + const blockedNote = await createNote(ctx, blocked, { text: 'hello blocked', visibility: 'public' }); + const otherNote = await createNote(ctx, other, { text: 'hello other', visibility: 'public' }); + + await createBlocking(ctx, me, blocked); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10 }); + expect(result.map(note => note.id).sort()).toEqual([otherNote.id, blockedNote.id].sort()); + }); + }); + + describe('pagination', () => { + test('paginates with sinceId', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const author = await createUser(ctx, { username: 'author', usernameLower: 'author', host: null }); + + const t1 = Date.now() - 3000; + const t2 = Date.now() - 2000; + const t3 = Date.now() - 1000; + + const note1 = await createNote(ctx, author, { text: 'hello' }, t1); + const note2 = await createNote(ctx, author, { text: 'hello' }, t2); + const note3 = await createNote(ctx, author, { text: 'hello' }, t3); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10, sinceId: note1.id }); + + const expected = sinceIdOrder === 'asc' + ? [note2.id, note3.id] + : [note3.id, note2.id]; + expect(result.map(note => note.id)).toEqual(expected); + }); + + test('paginates with untilId', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const author = await createUser(ctx, { username: 'author', usernameLower: 'author', host: null }); + + const t1 = Date.now() - 3000; + const t2 = Date.now() - 2000; + const t3 = Date.now() - 1000; + + const note1 = await createNote(ctx, author, { text: 'hello' }, t1); + const note2 = await createNote(ctx, author, { text: 'hello' }, t2); + const note3 = await createNote(ctx, author, { text: 'hello' }, t3); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10, untilId: note3.id }); + + expect(result.map(note => note.id)).toEqual([note2.id, note1.id]); + }); + + test('paginates with sinceId and untilId together', async () => { + const ctx = getCtx(); + const me = await createUser(ctx, { username: 'me', usernameLower: 'me', host: null }); + const author = await createUser(ctx, { username: 'author', usernameLower: 'author', host: null }); + + const t1 = Date.now() - 4000; + const t2 = Date.now() - 3000; + const t3 = Date.now() - 2000; + const t4 = Date.now() - 1000; + + const note1 = await createNote(ctx, author, { text: 'hello' }, t1); + const note2 = await createNote(ctx, author, { text: 'hello' }, t2); + const note3 = await createNote(ctx, author, { text: 'hello' }, t3); + const note4 = await createNote(ctx, author, { text: 'hello' }, t4); + + const result = await ctx.service.searchNote('hello', me, {}, { limit: 10, sinceId: note1.id, untilId: note4.id }); + + expect(result.map(note => note.id)).toEqual([note3.id, note2.id]); + }); + }); + }); + } + + describe('sqlLike', () => { + let ctx: TestContext; + + beforeAll(async () => { + ctx = await buildContext(); + }); + + afterAll(async () => { + await ctx.app.close(); + }); + + afterEach(async () => { + await cleanupContext(ctx); + }); + + defineSearchNoteTests(() => ctx, { supportsFollowersVisibility: true, sinceIdOrder: 'asc' }); + }); + + describe('meilisearch', () => { + let ctx: TestContext; + let meilisearch: MeiliSearch; + let meilisearchIndex: Index; + let meiliConfig: Config; + + beforeAll(async () => { + const baseConfig = loadConfig(); + meiliConfig = { + ...baseConfig, + fulltextSearch: { + provider: 'meilisearch', + }, + meilisearch: { + host: '127.0.0.1', + port: '57712', + apiKey: '', + index: 'test-search-service', + scope: 'global', + ssl: false, + }, + }; + + ctx = await buildContext(meiliConfig); + meilisearch = ctx.app.get(DI.meilisearch) as MeiliSearch; + meilisearchIndex = meilisearch.index(`${meiliConfig.meilisearch!.index}---notes`); + + const settingsTask = await meilisearchIndex.updateSettings(meilisearchSettings); + await meilisearch.tasks.waitForTask(settingsTask.taskUid); + + const clearTask = await meilisearchIndex.deleteAllDocuments(); + await meilisearch.tasks.waitForTask(clearTask.taskUid); + + ctx.indexer = async (note: MiNote) => { + if (note.text == null && note.cw == null) return; + if (!['home', 'public'].includes(note.visibility)) return; + if (meiliConfig.meilisearch?.scope === 'local' && note.userHost != null) return; + + const task = await meilisearchIndex.addDocuments([{ + id: note.id, + createdAt: ctx.idService.parse(note.id).date.getTime(), + userId: note.userId, + userHost: note.userHost, + channelId: note.channelId, + cw: note.cw, + text: note.text, + tags: note.tags, + }], { + primaryKey: 'id', + }); + await meilisearch.tasks.waitForTask(task.taskUid); + }; + }); + + afterAll(async () => { + await ctx.app.close(); + }); + + afterEach(async () => { + await cleanupContext(ctx); + const clearTask = await meilisearchIndex.deleteAllDocuments(); + await meilisearch.tasks.waitForTask(clearTask.taskUid); + }); + + defineSearchNoteTests(() => ctx, { supportsFollowersVisibility: false, sinceIdOrder: 'desc' }); + }); +}); diff --git a/packages/backend/test/unit/SigninWithPasskeyApiService.ts b/packages/backend/test/unit/SigninWithPasskeyApiService.ts index 0687ed8437..8ef46024ac 100644 --- a/packages/backend/test/unit/SigninWithPasskeyApiService.ts +++ b/packages/backend/test/unit/SigninWithPasskeyApiService.ts @@ -9,7 +9,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { FastifyReply, FastifyRequest } from 'fastify'; import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import { HttpHeader } from 'fastify/types/utils.js'; -import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; +import { MockMetadata, ModuleMocker } from 'jest-mock'; import { MiUser } from '@/models/User.js'; import { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; @@ -95,7 +95,7 @@ describe('SigninWithPasskeyApiService', () => { ], }).useMocker((token) => { if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const mockMetadata = moduleMocker.getMetadata(token) as MockMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); } diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts index 61187e9f2a..1128d83be1 100644 --- a/packages/backend/test/unit/SystemWebhookService.ts +++ b/packages/backend/test/unit/SystemWebhookService.ts @@ -101,8 +101,8 @@ describe('SystemWebhookService', () => { } async function afterEachImpl() { - await usersRepository.delete({}); - await systemWebhooksRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await systemWebhooksRepository.createQueryBuilder().delete().execute(); } // -------------------------------------------------------------------------------------- diff --git a/packages/backend/test/unit/UserSearchService.ts b/packages/backend/test/unit/UserSearchService.ts index 697425beb8..75d3e58adc 100644 --- a/packages/backend/test/unit/UserSearchService.ts +++ b/packages/backend/test/unit/UserSearchService.ts @@ -127,7 +127,7 @@ describe('UserSearchService', () => { }); afterEach(async () => { - await usersRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); }); afterAll(async () => { diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts index a2a85e9489..928b9d3c2b 100644 --- a/packages/backend/test/unit/UserWebhookService.ts +++ b/packages/backend/test/unit/UserWebhookService.ts @@ -95,8 +95,8 @@ describe('UserWebhookService', () => { } async function afterEachImpl() { - await usersRepository.delete({}); - await userWebhooksRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await userWebhooksRepository.createQueryBuilder().delete().execute(); } // -------------------------------------------------------------------------------------- diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts index 736aac40b4..0e965021c2 100644 --- a/packages/backend/test/unit/WebhookTestService.ts +++ b/packages/backend/test/unit/WebhookTestService.ts @@ -111,8 +111,8 @@ describe('WebhookTestService', () => { userWebhookService.fetchWebhooks.mockClear(); systemWebhookService.fetchSystemWebhooks.mockClear(); - await usersRepository.delete({}); - await userProfilesRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await userProfilesRepository.createQueryBuilder().delete().execute(); }); afterAll(async () => { diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 9df947982b..c6e09bdda2 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -6,9 +6,15 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; +import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; import { Test } from '@nestjs/testing'; import { jest } from '@jest/globals'; +import { MockResolver } from '../misc/mock-resolver.js'; +import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js'; +import type { MiRemoteUser } from '@/models/User.js'; import { ApImageService } from '@/core/activitypub/models/ApImageService.js'; import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; @@ -19,14 +25,14 @@ import { GlobalModule } from '@/GlobalModule.js'; import { CoreModule } from '@/core/CoreModule.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { LoggerService } from '@/core/LoggerService.js'; -import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js'; import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; -import type { MiRemoteUser } from '@/models/User.js'; import { genAidx } from '@/misc/id/aidx.js'; -import { MockResolver } from '../misc/mock-resolver.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); const host = 'https://host1.test'; @@ -120,7 +126,13 @@ describe('ActivityPub', () => { imports: [GlobalModule, CoreModule], }) .overrideProvider(DownloadService).useValue({ - async downloadUrl(): Promise<{ filename: string }> { + async downloadUrl(url: string, path: string): Promise<{ filename: string }> { + if (url.endsWith('.png')) { + fs.copyFileSync( + _dirname + '/../resources/hw.png', + path, + ); + } return { filename: 'dummy.tmp', }; @@ -440,7 +452,7 @@ describe('ActivityPub', () => { }); }); - describe('JSON-LD', () =>{ + describe('JSON-LD', () => { test('Compaction', async () => { const jsonLd = jsonLdService.use(); diff --git a/packages/backend/test/unit/chart.ts b/packages/backend/test/unit/chart.ts index 9dedd3a79d..364a2c2fbd 100644 --- a/packages/backend/test/unit/chart.ts +++ b/packages/backend/test/unit/chart.ts @@ -9,6 +9,7 @@ import * as assert from 'assert'; import { jest } from '@jest/globals'; import * as lolex from '@sinonjs/fake-timers'; import { DataSource } from 'typeorm'; +import * as Redis from 'ioredis'; import TestChart from '@/core/chart/charts/test.js'; import TestGroupedChart from '@/core/chart/charts/test-grouped.js'; import TestUniqueChart from '@/core/chart/charts/test-unique.js'; @@ -18,16 +19,16 @@ import { entity as TestGroupedChartEntity } from '@/core/chart/charts/entities/t import { entity as TestUniqueChartEntity } from '@/core/chart/charts/entities/test-unique.js'; import { entity as TestIntersectionChartEntity } from '@/core/chart/charts/entities/test-intersection.js'; import { loadConfig } from '@/config.js'; -import type { AppLockService } from '@/core/AppLockService.js'; import Logger from '@/logger.js'; describe('Chart', () => { const config = loadConfig(); - const appLockService = { - getChartInsertLock: () => () => Promise.resolve(() => {}), - } as unknown as jest.Mocked; let db: DataSource | undefined; + let redisClient = { + set: () => Promise.resolve('OK'), + get: () => Promise.resolve(null), + } as unknown as jest.Mocked; let testChart: TestChart; let testGroupedChart: TestGroupedChart; @@ -64,12 +65,14 @@ describe('Chart', () => { await db.initialize(); const logger = new Logger('chart'); // TODO: モックにする - testChart = new TestChart(db, appLockService, logger); - testGroupedChart = new TestGroupedChart(db, appLockService, logger); - testUniqueChart = new TestUniqueChart(db, appLockService, logger); - testIntersectionChart = new TestIntersectionChart(db, appLockService, logger); + testChart = new TestChart(db, redisClient, logger); + testGroupedChart = new TestGroupedChart(db, redisClient, logger); + testUniqueChart = new TestUniqueChart(db, redisClient, logger); + testIntersectionChart = new TestIntersectionChart(db, redisClient, logger); clock = lolex.install({ + // https://github.com/sinonjs/sinon/issues/2620 + toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[], now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)), shouldClearNativeTimers: true, }); diff --git a/packages/backend/test/unit/entities/DriveFileEntityService.ts b/packages/backend/test/unit/entities/DriveFileEntityService.ts new file mode 100644 index 0000000000..2e416326ee --- /dev/null +++ b/packages/backend/test/unit/entities/DriveFileEntityService.ts @@ -0,0 +1,227 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import { afterAll, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { Test } from '@nestjs/testing'; +import type { TestingModule } from '@nestjs/testing'; +import type { DriveFilesRepository, DriveFoldersRepository, UsersRepository } from '@/models/_.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { genAidx } from '@/misc/id/aidx.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; + +const describeBenchmark = process.env.RUN_BENCHMARKS === '1' ? describe : describe.skip; + +describe('DriveFileEntityService', () => { + let app: TestingModule; + let service: DriveFileEntityService; + let driveFolderEntityService: DriveFolderEntityService; + let driveFilesRepository: DriveFilesRepository; + let driveFoldersRepository: DriveFoldersRepository; + let usersRepository: UsersRepository; + let idCounter = 0; + + const userEntityServiceMock = { + packMany: jest.fn(async (users: Array) => { + return users.map(u => ({ + id: typeof u === 'string' ? u : u.id, + username: 'user', + })); + }), + pack: jest.fn(async (user: string | { id: string }) => { + return { + id: typeof user === 'string' ? user : user.id, + username: 'user', + }; + }), + }; + + const nextId = () => genAidx(Date.now() + (idCounter++)); + + const createUser = async () => { + const un = secureRndstr(16); + const id = nextId(); + await usersRepository.insert({ + id, + username: un, + usernameLower: un.toLowerCase(), + }); + return usersRepository.findOneByOrFail({ id }); + }; + + const createFolder = async (name: string, parentId: string | null) => { + const id = nextId(); + await driveFoldersRepository.insert({ + id, + name, + userId: null, + parentId, + }); + return driveFoldersRepository.findOneByOrFail({ id }); + }; + + const createFile = async (folderId: string | null, userId: string | null) => { + const id = nextId(); + await driveFilesRepository.insert({ + id, + userId, + userHost: null, + md5: secureRndstr(32), + name: `file-${id}`, + type: 'text/plain', + size: 1, + comment: null, + blurhash: null, + properties: {}, + storedInternal: true, + url: `https://example.com/${id}`, + thumbnailUrl: null, + webpublicUrl: null, + webpublicType: null, + accessKey: null, + thumbnailAccessKey: null, + webpublicAccessKey: null, + uri: null, + src: null, + folderId, + isSensitive: false, + maybeSensitive: false, + maybePorn: false, + isLink: false, + requestHeaders: null, + requestIp: null, + }); + return driveFilesRepository.findOneByOrFail({ id }); + }; + + beforeAll(async () => { + const moduleBuilder = Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + }); + moduleBuilder.overrideProvider(UserEntityService).useValue(userEntityServiceMock as any); + + app = await moduleBuilder.compile(); + await app.init(); + app.enableShutdownHooks(); + + service = app.get(DriveFileEntityService); + driveFolderEntityService = app.get(DriveFolderEntityService); + driveFilesRepository = app.get(DI.driveFilesRepository); + driveFoldersRepository = app.get(DI.driveFoldersRepository); + usersRepository = app.get(DI.usersRepository); + }); + + beforeEach(() => { + userEntityServiceMock.packMany.mockClear(); + userEntityServiceMock.pack.mockClear(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('pack', () => { + test('detail: false', async () => { + const user = await createUser(); + const folder = await createFolder('pack-root', null); + const file = await createFile(folder.id, user.id); + + const packed = await service.pack(file, { detail: false, self: true }) as any; + expect(packed.id).toBe(file.id); + expect(packed.folder).toBeNull(); + expect(packed.user).toBeNull(); + expect(packed.userId).toBeNull(); + }); + + test('detail: true', async () => { + const folder = await createFolder('pack-parent', null); + const child = await createFolder('pack-child', folder.id); + const file = await createFile(child.id, null); + + const packed = await service.pack(file, { detail: true, self: true }) as any; + expect(packed.folder?.id).toBe(child.id); + expect(packed.folder?.parent?.id).toBe(folder.id); + }); + }); + + describe('packNullable', () => { + test('returns null for missing', async () => { + const packed = await service.packNullable('non-existent' as any, { detail: false }); + expect(packed).toBeNull(); + }); + + test('uses packedUser hint when withUser', async () => { + const user = await createUser(); + const file = await createFile(null, user.id); + + const packed = await service.packNullable(file, { withUser: true, self: true }, { + packedUser: { id: user.id, username: 'hint' } as any, + }); + expect(packed?.user?.id).toBe(user.id); + expect(packed?.user?.username).toBe('hint'); + }); + }); + + describe('packMany', () => { + test('withUser: true uses deduped packMany', async () => { + const user = await createUser(); + const fileA = await createFile(null, user.id); + const fileB = await createFile(null, user.id); + + const packed = await service.packMany([fileA, fileB], { withUser: true, self: true }); + expect(packed.length).toBe(2); + expect(userEntityServiceMock.packMany).toHaveBeenCalledTimes(1); + expect(userEntityServiceMock.packMany.mock.calls[0]?.[0]?.length).toBe(1); + expect(packed[0]?.user?.id).toBe(user.id); + }); + + test('detail: true packs folder', async () => { + const folder = await createFolder('packmany-root', null); + const file = await createFile(folder.id, null); + + const packed = await service.packMany([file], { detail: true, self: true }); + expect(packed[0]?.folder?.id).toBe(folder.id); + expect(packed[0]?.folder?.parent).toBeUndefined(); + }); + + test('detail: true uses DriveFolderEntityService pack', async () => { + const folder = await createFolder('packmany-folder', null); + const file = await createFile(folder.id, null); + const packSpy = jest.spyOn(driveFolderEntityService, 'pack'); + + await service.packMany([file], { detail: true, self: true }); + expect(packSpy).toHaveBeenCalled(); + packSpy.mockRestore(); + }); + }); + + describeBenchmark('benchmark', () => { + test('packMany', async () => { + const user = await createUser(); + const folders = []; + for (let i = 0; i < 100; i++) { + folders.push(await createFolder(`bench-${i}`, null)); + } + const files = []; + for (const folder of folders) { + for (let j = 0; j < 20; j++) { + files.push(await createFile(folder.id, user.id)); + } + } + + const start = Date.now(); + await service.packMany(files, { detail: true, withUser: true, self: true }); + const elapsed = Date.now() - start; + + console.log(`DriveFileEntityService.packMany benchmark: ${elapsed}ms`); + }); + }); +}); diff --git a/packages/backend/test/unit/entities/DriveFolderEntityService.ts b/packages/backend/test/unit/entities/DriveFolderEntityService.ts new file mode 100644 index 0000000000..299ee5f42b --- /dev/null +++ b/packages/backend/test/unit/entities/DriveFolderEntityService.ts @@ -0,0 +1,171 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; +import { Test } from '@nestjs/testing'; +import type { TestingModule } from '@nestjs/testing'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { genAidx } from '@/misc/id/aidx.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; + +const describeBenchmark = process.env.RUN_BENCHMARKS === '1' ? describe : describe.skip; + +describe('DriveFolderEntityService', () => { + let app: TestingModule; + let service: DriveFolderEntityService; + let driveFoldersRepository: DriveFoldersRepository; + let driveFilesRepository: DriveFilesRepository; + let idCounter = 0; + + const nextId = () => genAidx(Date.now() + (idCounter++)); + + const createFolder = async (name: string, parentId: string | null) => { + const id = nextId(); + await driveFoldersRepository.insert({ + id, + name, + userId: null, + parentId, + }); + return driveFoldersRepository.findOneByOrFail({ id }); + }; + + const createFile = async (folderId: string | null) => { + const id = nextId(); + await driveFilesRepository.insert({ + id, + userId: null, + userHost: null, + md5: secureRndstr(32), + name: `file-${id}`, + type: 'text/plain', + size: 1, + comment: null, + blurhash: null, + properties: {}, + storedInternal: true, + url: `https://example.com/${id}`, + thumbnailUrl: null, + webpublicUrl: null, + webpublicType: null, + accessKey: null, + thumbnailAccessKey: null, + webpublicAccessKey: null, + uri: null, + src: null, + folderId, + isSensitive: false, + maybeSensitive: false, + maybePorn: false, + isLink: false, + requestHeaders: null, + requestIp: null, + }); + }; + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + }).compile(); + await app.init(); + app.enableShutdownHooks(); + + service = app.get(DriveFolderEntityService); + driveFoldersRepository = app.get(DI.driveFoldersRepository); + driveFilesRepository = app.get(DI.driveFilesRepository); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('pack', () => { + test('detail: false', async () => { + const root = await createFolder('root', null); + const child = await createFolder('child', root.id); + + const packed = await service.pack(child, { detail: false }) as any; + expect(packed.id).toBe(child.id); + expect(packed.parentId).toBe(root.id); + expect(packed.parent).toBeUndefined(); + expect(packed.foldersCount).toBeUndefined(); + expect(packed.filesCount).toBeUndefined(); + }); + + test('detail: true', async () => { + const root = await createFolder('root-detail', null); + const child = await createFolder('child-detail', root.id); + await createFolder('grandchild-detail', child.id); + await createFile(child.id); + await createFile(child.id); + + const packed = await service.pack(child, { detail: true }) as any; + expect(packed.id).toBe(child.id); + expect(packed.foldersCount).toBe(1); + expect(packed.filesCount).toBe(2); + expect(packed.parent?.id).toBe(root.id); + expect(packed.parent?.parent).toBeUndefined(); + }); + + test('detail: true reaches root for deep hierarchy', async () => { + const root = await createFolder('root-deep', null); + const level1 = await createFolder('level-1', root.id); + const level2 = await createFolder('level-2', level1.id); + const level3 = await createFolder('level-3', level2.id); + const level4 = await createFolder('level-4', level3.id); + const level5 = await createFolder('level-5', level4.id); + + const packed = await service.pack(level5, { detail: true }) as any; + expect(packed.id).toBe(level5.id); + expect(packed.parent?.id).toBe(level4.id); + expect(packed.parent?.parent?.id).toBe(level3.id); + expect(packed.parent?.parent?.parent?.id).toBe(level2.id); + expect(packed.parent?.parent?.parent?.parent?.id).toBe(level1.id); + expect(packed.parent?.parent?.parent?.parent?.parent?.id).toBe(root.id); + expect(packed.parent?.parent?.parent?.parent?.parent?.parent).toBeUndefined(); + }); + }); + + describe('packMany', () => { + test('preserves order and packs parents', async () => { + const root = await createFolder('root-many', null); + const childA = await createFolder('child-a', root.id); + const childB = await createFolder('child-b', root.id); + await createFolder('child-a-sub', childA.id); + await createFile(childA.id); + + const packed = await service.packMany([childB, childA], { detail: true }) as any; + expect(packed[0].id).toBe(childB.id); + expect(packed[1].id).toBe(childA.id); + expect(packed[0].parent?.id).toBe(root.id); + expect(packed[1].parent?.id).toBe(root.id); + expect(packed[0].filesCount).toBe(0); + expect(packed[1].filesCount).toBe(1); + expect(packed[0].foldersCount).toBe(0); + expect(packed[1].foldersCount).toBe(1); + }); + }); + + describeBenchmark('benchmark', () => { + test('packMany', async () => { + const root = await createFolder('bench-root', null); + const folders = []; + for (let i = 0; i < 200; i++) { + folders.push(await createFolder(`bench-${i}`, root.id)); + } + + const start = Date.now(); + await service.packMany(folders, { detail: true }); + const elapsed = Date.now() - start; + console.log(`DriveFolderEntityService.packMany benchmark: ${elapsed}ms`); + }); + }); +}); diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts index ce3f931bb0..ca6a639be8 100644 --- a/packages/backend/test/unit/entities/UserEntityService.ts +++ b/packages/backend/test/unit/entities/UserEntityService.ts @@ -232,7 +232,7 @@ describe('UserEntityService', () => { }); test('MeDetailed', async() => { - const achievements = [{ name: 'achievement', unlockedAt: new Date().getTime() }]; + const achievements = [{ name: 'iLoveMisskey' as const, unlockedAt: new Date().getTime() }]; const me = await createUser({}, { birthday: '2000-01-01', achievements: achievements, diff --git a/packages/backend/test/unit/misc/is-renote.ts b/packages/backend/test/unit/misc/is-renote.ts index 0b713e8bf6..3c628d8298 100644 --- a/packages/backend/test/unit/misc/is-renote.ts +++ b/packages/backend/test/unit/misc/is-renote.ts @@ -23,6 +23,7 @@ const base: MiNote = { renoteCount: 0, repliesCount: 0, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, @@ -43,6 +44,7 @@ const base: MiNote = { replyUserHost: null, renoteUserId: null, renoteUserHost: null, + renoteChannelId: null, }; describe('misc:is-renote', () => { diff --git a/packages/backend/test/unit/misc/should-hide-note-by-time.ts b/packages/backend/test/unit/misc/should-hide-note-by-time.ts new file mode 100644 index 0000000000..1c463c82c6 --- /dev/null +++ b/packages/backend/test/unit/misc/should-hide-note-by-time.ts @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { describe, expect, test, beforeEach, afterEach } from '@jest/globals'; +import * as lolex from '@sinonjs/fake-timers'; +import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js'; + +describe('misc:should-hide-note-by-time', () => { + let clock: lolex.InstalledClock; + const epoch = Date.UTC(2000, 0, 1, 0, 0, 0); + + beforeEach(() => { + clock = lolex.install({ + // https://github.com/sinonjs/sinon/issues/2620 + toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[], + now: new Date(epoch), + shouldClearNativeTimers: true, + }); + }); + + afterEach(() => { + clock.uninstall(); + }); + + describe('hiddenBefore が null または undefined の場合', () => { + test('hiddenBefore が null のときは false を返す(非表示機能が有効でない)', () => { + const createdAt = new Date(epoch - 86400000); // 1 day ago + expect(shouldHideNoteByTime(null, createdAt)).toBe(false); + }); + + test('hiddenBefore が undefined のときは false を返す(非表示機能が有効でない)', () => { + const createdAt = new Date(epoch - 86400000); // 1 day ago + expect(shouldHideNoteByTime(undefined, createdAt)).toBe(false); + }); + }); + + describe('相対時間モード (hiddenBefore <= 0)', () => { + test('閾値内に作成されたノートは false を返す(作成からの経過時間がまだ短い→表示)', () => { + const hiddenBefore = -86400; // 1 day in seconds + const createdAt = new Date(epoch - 3600000); // 1 hour ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(false); + }); + + test('閾値を超えて作成されたノートは true を返す(指定期間以上経過している→非表示)', () => { + const hiddenBefore = -86400; // 1 day in seconds + const createdAt = new Date(epoch - 172800000); // 2 days ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true); + }); + + test('ちょうど閾値で作成されたノートは true を返す(閾値に達したら非表示)', () => { + const hiddenBefore = -86400; // 1 day in seconds + const createdAt = new Date(epoch - 86400000); // exactly 1 day ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true); + }); + + test('異なる相対時間値で判定できる(1時間設定と3時間設定の異なる結果)', () => { + const createdAt = new Date(epoch - 7200000); // 2 hours ago + expect(shouldHideNoteByTime(-3600, createdAt)).toBe(true); // 1時間経過→非表示 + expect(shouldHideNoteByTime(-10800, createdAt)).toBe(false); // 3時間未経過→表示 + }); + + test('ISO 8601 形式の文字列の createdAt に対応できる(文字列でも正しく判定)', () => { + const createdAtString = new Date(epoch - 86400000).toISOString(); + const hiddenBefore = -86400; // 1 day in seconds + expect(shouldHideNoteByTime(hiddenBefore, createdAtString)).toBe(true); + }); + + test('hiddenBefore が 0 の場合に対応できる(0秒以上経過で非表示→ほぼ全て非表示)', () => { + const hiddenBefore = 0; + const createdAt = new Date(epoch - 1); // 1ms ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true); + }); + }); + + describe('絶対時間モード (hiddenBefore > 0)', () => { + test('閾値タイムスタンプより後に作成されたノートは false を返す(指定日時より後→表示)', () => { + const thresholdSeconds = Math.floor(epoch / 1000); + const createdAt = new Date(epoch + 3600000); // 1 hour from epoch + expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(false); + }); + + test('閾値タイムスタンプより前に作成されたノートは true を返す(指定日時より前→非表示)', () => { + const thresholdSeconds = Math.floor(epoch / 1000); + const createdAt = new Date(epoch - 3600000); // 1 hour ago + expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true); + }); + + test('ちょうど閾値タイムスタンプで作成されたノートは true を返す(指定日時に達したら非表示)', () => { + const thresholdSeconds = Math.floor(epoch / 1000); + const createdAt = new Date(epoch); // exactly epoch + expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true); + }); + + test('ISO 8601 形式の文字列の createdAt に対応できる(文字列でも正しく判定)', () => { + const thresholdSeconds = Math.floor(epoch / 1000); + const createdAtString = new Date(epoch - 3600000).toISOString(); + expect(shouldHideNoteByTime(thresholdSeconds, createdAtString)).toBe(true); + }); + + test('異なる閾値タイムスタンプで判定できる(2021年設定と現在より1時間前設定の異なる結果)', () => { + const thresholdSeconds = Math.floor((epoch - 86400000) / 1000); // 1 day ago + const createdAtBefore = new Date(epoch - 172800000); // 2 days ago + const createdAtAfter = new Date(epoch - 3600000); // 1 hour ago + expect(shouldHideNoteByTime(thresholdSeconds, createdAtBefore)).toBe(true); // 閾値より前→非表示 + expect(shouldHideNoteByTime(thresholdSeconds, createdAtAfter)).toBe(false); // 閾値より後→表示 + }); + }); + + describe('エッジケース', () => { + test('相対時間モードで非常に古いノートに対応できる(非常に古い→閾値超→非表示)', () => { + const hiddenBefore = -1; // hide notes older than 1 second + const createdAt = new Date(epoch - 1000000); // very old + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true); + }); + + test('相対時間モードで非常に新しいノートに対応できる(非常に新しい→閾値未満→表示)', () => { + const hiddenBefore = -86400; // 1 day + const createdAt = new Date(epoch - 1); // 1ms ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(false); + }); + + test('大きなタイムスタンプ値に対応できる(未来の日時を指定→現在のノートは全て非表示)', () => { + const thresholdSeconds = Math.floor(epoch / 1000) + 86400; // 1 day from epoch + const createdAt = new Date(epoch); // created epoch + expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true); + }); + + test('小さな相対時間値に対応できる(1秒設定で2秒前→非表示)', () => { + const hiddenBefore = -1; // 1 second + const createdAt = new Date(epoch - 2000); // 2 seconds ago + expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true); + }); + }); +}); diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts index 07618e7762..01a36c9fef 100644 --- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts +++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -141,6 +141,8 @@ describe('CheckModeratorsActivityProcessorService', () => { beforeEach(async () => { clock = lolex.install({ + // https://github.com/sinonjs/sinon/issues/2620 + toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[], now: new Date(baseDate), shouldClearNativeTimers: true, }); @@ -157,8 +159,8 @@ describe('CheckModeratorsActivityProcessorService', () => { afterEach(async () => { clock.uninstall(); - await usersRepository.delete({}); - await userProfilesRepository.delete({}); + await usersRepository.createQueryBuilder().delete().execute(); + await userProfilesRepository.createQueryBuilder().delete().execute(); roleService.getModerators.mockReset(); announcementService.create.mockReset(); emailService.sendEmail.mockReset(); diff --git a/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts new file mode 100644 index 0000000000..631e160afc --- /dev/null +++ b/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts @@ -0,0 +1,652 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import ms from 'ms'; +import { + type MiNote, + type MiUser, + type NotesRepository, + type NoteFavoritesRepository, + type UserNotePiningsRepository, + type UsersRepository, + type UserProfilesRepository, + MiMeta, +} from '@/models/_.js'; +import { CleanRemoteNotesProcessorService } from '@/queue/processors/CleanRemoteNotesProcessorService.js'; +import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; + +describe('CleanRemoteNotesProcessorService', () => { + let app: TestingModule; + let service: CleanRemoteNotesProcessorService; + let idService: IdService; + let notesRepository: NotesRepository; + let noteFavoritesRepository: NoteFavoritesRepository; + let userNotePiningsRepository: UserNotePiningsRepository; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + + // Local user + let alice: MiUser; + // Remote user 1 + let bob: MiUser; + // Remote user 2 + let carol: MiUser; + + const meta = new MiMeta(); + + // Mock job object + const createMockJob = () => ({ + log: jest.fn(), + updateProgress: jest.fn(), + }); + + async function createUser(data: Partial = {}) { + const id = idService.gen(); + const un = data.username || secureRndstr(16); + const user = await usersRepository + .insert({ + id, + username: un, + usernameLower: un.toLowerCase(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.save({ + userId: id, + }); + + return user; + } + + async function createNote(data: Partial, user: MiUser, time?: number): Promise { + const id = idService.gen(time); + const note = await notesRepository + .insert({ + id: id, + text: `note_${id}`, + userId: user.id, + userHost: user.host, + visibility: 'public', + ...data, + }) + .then(x => notesRepository.findOneByOrFail(x.identifiers[0])); + return note; + } + + beforeAll(async () => { + app = await Test + .createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + CleanRemoteNotesProcessorService, + IdService, + { + provide: QueueLoggerService, + useFactory: () => ({ + logger: { + createSubLogger: () => ({ + info: jest.fn(), + warn: jest.fn(), + succ: jest.fn(), + }), + }, + }), + }, + ], + }) + .overrideProvider(DI.meta).useFactory({ factory: () => meta }) + .compile(); + + service = app.get(CleanRemoteNotesProcessorService); + idService = app.get(IdService); + notesRepository = app.get(DI.notesRepository); + noteFavoritesRepository = app.get(DI.noteFavoritesRepository); + userNotePiningsRepository = app.get(DI.userNotePiningsRepository); + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + + alice = await createUser({ username: 'alice', host: null }); + bob = await createUser({ username: 'bob', host: 'remote1.example.com' }); + carol = await createUser({ username: 'carol', host: 'remote2.example.com' }); + + app.enableShutdownHooks(); + }); + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks(); + + // Set default meta values + meta.enableRemoteNotesCleaning = true; + meta.remoteNotesCleaningMaxProcessingDurationInMinutes = 0.3; + meta.remoteNotesCleaningExpiryDaysForEachNotes = 30; + }, 60 * 1000); + + afterEach(async () => { + // Clean up test data + await Promise.all([ + notesRepository.createQueryBuilder().delete().execute(), + userNotePiningsRepository.createQueryBuilder().delete().execute(), + noteFavoritesRepository.createQueryBuilder().delete().execute(), + ]); + }, 60 * 1000); + + afterAll(async () => { + await app.close(); + }); + + describe('basic', () => { + test('should skip cleaning when enableRemoteNotesCleaning is false', async () => { + meta.enableRemoteNotesCleaning = false; + const job = createMockJob(); + + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 0, + oldest: null, + newest: null, + skipped: true, + transientErrors: 0, + }); + }); + + test('should return success result when enableRemoteNotesCleaning is true and no notes to clean', async () => { + const job = createMockJob(); + + await createNote({}, alice); + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 0, + oldest: null, + newest: null, + skipped: false, + transientErrors: 0, + }); + }, 3000); + + test('should clean remote notes and return stats', async () => { + // Remote notes + const remoteNotes = await Promise.all([ + createNote({}, bob), + createNote({}, carol), + createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000), + createNote({}, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000), // Note older than expiry + ]); + + // Local notes + const localNotes = await Promise.all([ + createNote({}, alice), + createNote({}, alice, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000), + ]); + + const job = createMockJob(); + + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 2, + oldest: expect.any(Number), + newest: expect.any(Number), + skipped: false, + transientErrors: 0, + }); + + // Check side-by-side from all notes + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(4); + expect(remainingNotes.some(n => n.id === remoteNotes[0].id)).toBe(true); + expect(remainingNotes.some(n => n.id === remoteNotes[1].id)).toBe(true); + expect(remainingNotes.some(n => n.id === remoteNotes[2].id)).toBe(false); + expect(remainingNotes.some(n => n.id === remoteNotes[3].id)).toBe(false); + expect(remainingNotes.some(n => n.id === localNotes[0].id)).toBe(true); + expect(remainingNotes.some(n => n.id === localNotes[1].id)).toBe(true); + }); + }); + + describe('advanced', () => { + // お気に入り + test('should not delete note that is favorited by any user', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Favorite the note + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: olderRemoteNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: olderRemoteNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // ピン留め + test('should not delete note that is pinned by the user', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Pin the note by the user who created it + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: bob.id, // Same user as the note creator + noteId: olderRemoteNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: olderRemoteNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // クリップ + test('should not delete note that is clipped', async () => { + const job = createMockJob(); + + // Create old remote note that is clipped + const clippedNote = await createNote({ + clippedCount: 1, // Clipped + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: clippedNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // ページ + test('should not delete note that is embedded in a page', async () => { + const job = createMockJob(); + + // Create old remote note that is embedded in a page + const clippedNote = await createNote({ + pageCount: 1, // Embedded in a page + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: clippedNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // 古いreply, renoteが含まれている時の挙動 + test('should handle reply/renote relationships correctly', async () => { + const job = createMockJob(); + + // Create old remote notes with reply/renote relationships + const originalNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + const replyNote = await createNote({ + replyId: originalNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + const renoteNote = await createNote({ + renoteId: originalNote.id, + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 3000); + + const result = await service.process(job as any); + + // Should delete all three notes as they are all old and remote + expect(result.deletedCount).toBe(3); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === originalNote.id)).toBe(false); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(false); + expect(remainingNotes.some(n => n.id === renoteNote.id)).toBe(false); + }); + + // 古いリモートノートに新しいリプライがある時、どちらも削除されない + test('should not delete both old remote note with new reply', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const oldNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const recentReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) + 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === oldNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === recentReplyNote.id)).toBe(true); // Recent reply note should remain + }); + + // 古いリモートノートに新しいリプライと古いリプライがある時、全て残る + test('should not delete old remote note with new reply and old reply', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const oldNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const recentReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) + 1000); + + // Create an old reply note that should be deleted + const oldReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === oldNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === recentReplyNote.id)).toBe(true); // Recent reply note should remain + expect(remainingNotes.some(n => n.id === oldReplyNote.id)).toBe(true); // Old reply note should be deleted + }); + + // リプライがお気に入りされているとき、どちらも削除されない + test('should not delete reply note that is favorited', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + // Favorite the reply note + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: replyNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); // Recent reply note should remain + }); + + // リプライがピン留めされているとき、どちらも削除されない + test('should not delete reply note that is pinned', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + // Pin the reply note + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: carol.id, + noteId: replyNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); // Reply note should remain + }); + + // リプライがクリップされているとき、どちらも削除されない + test('should not delete reply note that is clipped', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is old but clipped + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + clippedCount: 1, // Clipped + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Both notes should be kept because reply is clipped + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); + }); + + test('should handle mixed scenarios with multiple conditions', async () => { + const job = createMockJob(); + + // Create various types of notes + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + + // Should be deleted: old remote note with no special conditions + const deletableNote = await createNote({}, bob, oldTime); + + // Should NOT be deleted: old remote note but favorited + const favoritedNote = await createNote({}, carol, oldTime); + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: favoritedNote.id, + }); + + // Should NOT be deleted: old remote note but pinned + const pinnedNote = await createNote({}, bob, oldTime); + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: bob.id, + noteId: pinnedNote.id, + }); + + // Should NOT be deleted: old remote note but clipped + const clippedNote = await createNote({ + clippedCount: 2, + }, carol, oldTime); + + // Should NOT be deleted: old local note + const localNote = await createNote({}, alice, oldTime); + + // Should NOT be deleted: new remote note + const newerRemoteNote = await createNote({}, bob); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(1); // Only deletableNote should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(5); + expect(remainingNotes.some(n => n.id === deletableNote.id)).toBe(false); // Deleted + expect(remainingNotes.some(n => n.id === favoritedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === pinnedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === clippedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === localNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === newerRemoteNote.id)).toBe(true); // Kept + }); + + // 大量のノート + test('should handle large number of notes correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i); + noteIds.push(note.id); + } + + const result = await service.process(job as any); + + // Should delete all notes, but may require multiple batches + expect(result.deletedCount).toBe(AMOUNT); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(0); + }); + + // 大量のノート + リプライ or リノート + test('should handle large number of notes with replies correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i - AMOUNT); + noteIds.push(note.id); + if (i % 2 === 0) { + // Create a reply for every second note + await createNote({ replyId: note.id }, carol, oldTime - i); + } else { + // Create a renote for every second note + await createNote({ renoteId: note.id }, bob, oldTime - i); + } + } + + const result = await service.process(job as any); + // Should delete all notes, but may require multiple batches + expect(result.deletedCount).toBe(AMOUNT * 2); + expect(result.skipped).toBe(false); + }); + + // 大量の古いノート + 新しいリプライ or リノート + test('should handle large number of old notes with new replies correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const newTime = Date.now(); + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i); + noteIds.push(note.id); + if (i % 2 === 0) { + // Create a reply for every second note + await createNote({ replyId: note.id }, carol, newTime + i); + } else { + // Create a renote for every second note + await createNote({ renoteId: note.id }, bob, newTime + i); + } + } + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + }); + + // 大量の残す対象(clippedCount: 1)と大量の削除対象 + test('should handle large number of notes, mixed conditions with clippedCount', async () => { + const AMOUNT_BASE = 70; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT_BASE; i++) { + const note = await createNote({ clippedCount: 1 }, bob, oldTime - i - AMOUNT_BASE); + noteIds.push(note.id); + } + for (let i = 0; i < AMOUNT_BASE; i++) { + const note = await createNote({}, carol, oldTime - i); + noteIds.push(note.id); + } + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(AMOUNT_BASE); // Assuming half are deletable + expect(result.skipped).toBe(false); + }); + + // 大量の残す対象(リプライ)と大量の削除対象 + test('should handle large number of notes, mixed conditions with replies', async () => { + const AMOUNT_BASE = 70; + const job = createMockJob(); + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const newTime = Date.now(); + for (let i = 0; i < AMOUNT_BASE; i++) { + // should remain + const note = await createNote({}, carol, oldTime - AMOUNT_BASE - i); + // should remain + await createNote({ replyId: note.id }, bob, newTime + i); + } + + const noteIdsExpectedToBeDeleted = []; + for (let i = 0; i < AMOUNT_BASE; i++) { + // should be deleted + const note = await createNote({}, bob, oldTime - i); + noteIdsExpectedToBeDeleted.push(note.id); + } + + const result = await service.process(job as any); + expect(result.deletedCount).toBe(AMOUNT_BASE); // Assuming all replies are deletable + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(AMOUNT_BASE * 2); // Only replies should remain + noteIdsExpectedToBeDeleted.forEach(id => { + expect(remainingNotes.some(n => n.id === id)).toBe(false); // All original notes should be deleted + }); + }); + + test('should update cursor correctly during batch processing', async () => { + const job = createMockJob(); + + // Create notes with specific timing to test cursor behavior + const baseTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 10000; + + const note1 = await createNote({}, bob, baseTime); + const note2 = await createNote({}, carol, baseTime - 1000); + const note3 = await createNote({}, bob, baseTime - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(3); + expect(result.newest).toBe(idService.parse(note1.id).date.getTime()); + expect(result.oldest).toBe(idService.parse(note3.id).date.getTime()); + expect(result.skipped).toBe(false); + }); + }); +}); diff --git a/packages/backend/test/unit/server/FileServerService.ts b/packages/backend/test/unit/server/FileServerService.ts new file mode 100644 index 0000000000..c88175c5c7 --- /dev/null +++ b/packages/backend/test/unit/server/FileServerService.ts @@ -0,0 +1,770 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import fastifyStatic from '@fastify/static'; +import Fastify, { type FastifyInstance } from 'fastify'; +import { describe, expect, test } from '@jest/globals'; +import sharp from 'sharp'; +import { DataSource, type Repository } from 'typeorm'; +import { initTestDb, randomString } from '../../utils.js'; +import type { AiService } from '@/core/AiService.js'; +import { DownloadService } from '@/core/DownloadService.js'; +import { FileInfoService } from '@/core/FileInfoService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { ImageProcessingService } from '@/core/ImageProcessingService.js'; +import { InternalStorageService } from '@/core/InternalStorageService.js'; +import { IdService } from '@/core/IdService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { VideoProcessingService } from '@/core/VideoProcessingService.js'; +import { loadConfig, type Config } from '@/config.js'; +import { MiDriveFile } from '@/models/DriveFile.js'; +import { FileServerService } from '@/server/FileServerService.js'; + +const dummyPath = path.resolve('test/resources/dummy-for-file-server-service.png'); +const dummySize = fs.statSync(dummyPath).size; +const dummyBuffer = fs.readFileSync(dummyPath); +const svgBuffer = Buffer.from('', 'utf8'); +const textBuffer = Buffer.from('dummy text', 'utf8'); + +async function createRemoteFileServer() { + const flatPngBuffer = await sharp({ + create: { width: 8, height: 8, channels: 3, background: { r: 0, g: 0, b: 0 } }, + }).png().toBuffer(); + const server = Fastify(); + + server.get('/dummy.png', async (_request, reply) => { + reply.header('Content-Type', 'image/png'); + reply.header('Content-Length', String(dummyBuffer.length)); + return reply.send(dummyBuffer); + }); + + server.get('/dummy.svg', async (_request, reply) => { + reply.header('Content-Type', 'image/svg+xml'); + reply.header('Content-Length', String(svgBuffer.length)); + return reply.send(svgBuffer); + }); + + server.get('/dummy.txt', async (_request, reply) => { + reply.header('Content-Type', 'text/plain'); + reply.header('Content-Length', String(textBuffer.length)); + return reply.send(textBuffer); + }); + + server.get('/flat.png', async (_request, reply) => { + reply.header('Content-Type', 'image/png'); + reply.header('Content-Length', String(flatPngBuffer.length)); + return reply.send(flatPngBuffer); + }); + + const baseUrl = await server.listen({ port: 0, host: '127.0.0.1' }); + + return { + server, + pngUrl: `${baseUrl}/dummy.png`, + svgUrl: `${baseUrl}/dummy.svg`, + textUrl: `${baseUrl}/dummy.txt`, + flatPngUrl: `${baseUrl}/flat.png`, + }; +} + +describe('FileServerService', () => { + let db: DataSource; + let fastify: FastifyInstance; + let externalFastify: FastifyInstance; + let driveFilesRepository: Repository; + let internalStorageService: InternalStorageService; + let idService: IdService; + let config: Config; + let fileServerService: FileServerService; + let externalFileServerService: FileServerService; + let remoteServer: FastifyInstance; + let remotePngUrl: string; + let remoteSvgUrl: string; + let remoteTextUrl: string; + let remoteFlatPngUrl: string; + const storedPaths: string[] = []; + let createdFallbackAssets = false; + let fallbackAssetsDir = ''; + + function writeInternalFile(key: string) { + const dest = internalStorageService.resolvePath(key); + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(dummyPath, dest); + storedPaths.push(dest); + } + + async function insertDriveFile(params: { + accessKey: string; + thumbnailAccessKey?: string | null; + webpublicAccessKey?: string | null; + storedInternal: boolean; + isLink: boolean; + uri?: string | null; + name?: string; + type?: string; + size?: number; + }) { + const accessKey = params.accessKey; + const url = params.uri ?? `${config.url}/files/${accessKey}`; + await driveFilesRepository.insert({ + id: idService.gen(), + userId: null, + userHost: null, + md5: '00000000000000000000000000000000', + name: params.name ?? 'dummy.png', + type: params.type ?? 'image/png', + size: params.size ?? dummySize, + comment: null, + blurhash: null, + properties: {}, + storedInternal: params.storedInternal, + url, + thumbnailUrl: null, + webpublicUrl: null, + webpublicType: null, + accessKey, + thumbnailAccessKey: params.thumbnailAccessKey ?? null, + webpublicAccessKey: params.webpublicAccessKey ?? null, + uri: params.uri ?? null, + src: null, + folderId: null, + isSensitive: false, + maybeSensitive: false, + maybePorn: false, + isLink: params.isLink, + requestHeaders: {}, + requestIp: null, + }); + } + + beforeAll(async () => { + config = loadConfig(); + db = await initTestDb(false); + driveFilesRepository = db.getRepository(MiDriveFile); + + const loggerService = new LoggerService(); + const aiService = { + detectSensitive: async () => null, + } as unknown as AiService; + const fileInfoService = new FileInfoService(aiService, loggerService); + const httpRequestService = new HttpRequestService(config); + const downloadService = new DownloadService(config, httpRequestService, loggerService); + const imageProcessingService = new ImageProcessingService(); + const videoProcessingService = new VideoProcessingService(config, imageProcessingService); + internalStorageService = new InternalStorageService(config); + idService = new IdService(config); + fileServerService = new FileServerService( + config, + driveFilesRepository as any, + fileInfoService, + downloadService, + imageProcessingService, + videoProcessingService, + internalStorageService, + loggerService, + ); + + fastify = Fastify(); + await fastify.register(fastifyStatic, { + root: path.resolve('src/server/assets'), + serve: false, + }); + fileServerService.createServer(fastify, {}, () => {}); + await fastify.ready(); + + const externalConfig = { + ...config, + mediaProxy: 'https://media-proxy.test', + externalMediaProxyEnabled: true, + } as Config; + externalFileServerService = new FileServerService( + externalConfig, + driveFilesRepository as any, + fileInfoService, + downloadService, + imageProcessingService, + videoProcessingService, + internalStorageService, + loggerService, + ); + externalFastify = Fastify(); + await externalFastify.register(fastifyStatic, { + root: path.resolve('src/server/assets'), + serve: false, + }); + externalFileServerService.createServer(externalFastify, {}, () => {}); + await externalFastify.ready(); + + const remoteServerInfo = await createRemoteFileServer(); + remoteServer = remoteServerInfo.server; + remotePngUrl = remoteServerInfo.pngUrl; + remoteSvgUrl = remoteServerInfo.svgUrl; + remoteTextUrl = remoteServerInfo.textUrl; + remoteFlatPngUrl = remoteServerInfo.flatPngUrl; + + fallbackAssetsDir = path.resolve('src/server/file/assets'); + if (!fs.existsSync(fallbackAssetsDir)) { + fs.mkdirSync(fallbackAssetsDir, { recursive: true }); + fs.copyFileSync(dummyPath, path.join(fallbackAssetsDir, 'dummy.png')); + createdFallbackAssets = true; + } + }); + + afterEach(async () => { + await driveFilesRepository.createQueryBuilder().delete().execute(); + for (const filePath of storedPaths) { + try { + fs.unlinkSync(filePath); + } catch { + // NOP + } + } + storedPaths.length = 0; + }); + + afterAll(async () => { + await fastify.close(); + await externalFastify.close(); + await remoteServer.close(); + await db.destroy(); + if (createdFallbackAssets) { + fs.rmSync(fallbackAssetsDir, { recursive: true, force: true }); + } + }); + + describe('GET /files/app-default.jpg', () => { + test('GET /files/app-default.jpg ヘッダを検証する', async () => { + const prevNodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'test'; + + try { + const res = await fastify.inject({ + method: 'GET', + url: '/files/app-default.jpg', + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-type']).toBe('image/jpeg'); + expect(res.headers['access-control-allow-origin']).toBeUndefined(); + } finally { + process.env.NODE_ENV = prevNodeEnv; + } + }); + + test('GET /files/app-default.jpg development で CORS を許可する', async () => { + const prevNodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + try { + const res = await fastify.inject({ + method: 'GET', + url: '/files/app-default.jpg', + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['access-control-allow-origin']).toBe('*'); + } finally { + process.env.NODE_ENV = prevNodeEnv; + } + }); + + test('GET /files/app-default.jpg?x=1 クエリを除去してリダイレクトする', async () => { + const res = await fastify.inject({ + method: 'GET', + url: '/files/app-default.jpg?x=1', + }); + + expect(res.statusCode).toBe(301); + expect(res.headers.location).toBe('/files/app-default.jpg'); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + }); + }); + + describe('GET /files/:key', () => { + test('GET /files/:key 404 のときダミー画像を返す', async () => { + const accessKey = randomString(); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + }); + + expect(res.statusCode).toBe(404); + expect(res.headers['cache-control']).toBe('max-age=86400'); + }); + + test('GET /files/:key 画像配信ヘッダを検証する', async () => { + const accessKey = randomString(); + writeInternalFile(accessKey); + await insertDriveFile({ + accessKey, + storedInternal: true, + isLink: false, + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['content-length']).toBe(String(dummySize)); + expect(res.headers['content-disposition'] ?? '').toMatch(/^inline;/); + }); + + test('GET /files/:key Range で部分配信する', async () => { + const accessKey = randomString(); + writeInternalFile(accessKey); + await insertDriveFile({ + accessKey, + storedInternal: true, + isLink: false, + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + headers: { + range: 'bytes=0-3', + }, + }); + + expect(res.statusCode).toBe(206); + expect(res.headers['content-range']).toBe(`bytes 0-3/${dummySize}`); + expect(res.headers['accept-ranges']).toBe('bytes'); + expect(res.headers['content-length']).toBe('4'); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + }); + + test('GET /files/:key Range の終端を補正する', async () => { + const accessKey = randomString(); + writeInternalFile(accessKey); + await insertDriveFile({ + accessKey, + storedInternal: true, + isLink: false, + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + headers: { + range: 'bytes=0-999999', + }, + }); + + expect(res.statusCode).toBe(206); + expect(res.headers['content-range']).toBe(`bytes 0-${dummySize - 1}/${dummySize}`); + expect(res.headers['accept-ranges']).toBe('bytes'); + expect(res.headers['content-length']).toBe(String(dummySize)); + }); + + test('GET /files/:key thumbnail の Range で部分配信する', async () => { + const accessKey = randomString(); + const thumbnailKey = randomString(); + writeInternalFile(thumbnailKey); + await insertDriveFile({ + accessKey, + thumbnailAccessKey: thumbnailKey, + storedInternal: true, + isLink: false, + name: 'sample.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${thumbnailKey}`, + headers: { + range: 'bytes=0-3', + }, + }); + + expect(res.statusCode).toBe(206); + expect(res.headers['content-range']).toBe(`bytes 0-3/${dummySize}`); + expect(res.headers['accept-ranges']).toBe('bytes'); + expect(res.headers['content-length']).toBe('4'); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + }); + + test('GET /files/:key thumbnail のファイル名を整形する', async () => { + const accessKey = randomString(); + const thumbnailKey = randomString(); + writeInternalFile(thumbnailKey); + await insertDriveFile({ + accessKey, + thumbnailAccessKey: thumbnailKey, + storedInternal: true, + isLink: false, + name: 'sample.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${thumbnailKey}`, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('sample-thumb.png'); + }); + + test('GET /files/:key webpublic のファイル名を整形する', async () => { + const accessKey = randomString(); + const webpublicKey = randomString(); + writeInternalFile(webpublicKey); + await insertDriveFile({ + accessKey, + webpublicAccessKey: webpublicKey, + storedInternal: true, + isLink: false, + name: 'sample.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${webpublicKey}`, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('sample-web.png'); + }); + + test('GET /files/:key browsersafe でない MIME は octet-stream になる', async () => { + const accessKey = randomString(); + writeInternalFile(accessKey); + await insertDriveFile({ + accessKey, + storedInternal: true, + isLink: false, + type: 'application/x-msdownload', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('application/octet-stream'); + }); + + test('GET /files/:key 204 のときキャッシュ制御を返す', async () => { + const accessKey = randomString(); + await insertDriveFile({ + accessKey, + storedInternal: false, + isLink: false, + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + }); + + expect(res.statusCode).toBe(204); + expect(res.headers['cache-control']).toBe('max-age=86400'); + }); + + test('GET /files/:key 外部リンクを取得して配信する', async () => { + const accessKey = randomString(); + await insertDriveFile({ + accessKey, + storedInternal: false, + isLink: true, + uri: remotePngUrl, + name: 'remote.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-length']).toBe(String(dummyBuffer.length)); + expect(res.headers['content-disposition'] ?? '').toContain('remote.png'); + }); + + test('GET /files/:key 外部リンクを Range で部分配信する', async () => { + const accessKey = randomString(); + await insertDriveFile({ + accessKey, + storedInternal: false, + isLink: true, + uri: remotePngUrl, + name: 'remote.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${accessKey}`, + headers: { + range: 'bytes=0-3', + }, + }); + + expect(res.statusCode).toBe(206); + expect(res.headers['content-range']).toBe(`bytes 0-3/${dummyBuffer.length}`); + expect(res.headers['accept-ranges']).toBe('bytes'); + expect(res.headers['content-length']).toBe(String(dummyBuffer.length)); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + }); + + test('GET /files/:key thumbnail は mediaProxy/static.webp にリダイレクトする', async () => { + const accessKey = randomString(); + const thumbnailKey = randomString(); + await insertDriveFile({ + accessKey, + thumbnailAccessKey: thumbnailKey, + storedInternal: false, + isLink: true, + uri: remotePngUrl, + name: 'remote.png', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${thumbnailKey}`, + }); + + expect(res.statusCode).toBe(301); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers.location).toContain(`${config.mediaProxy}/static.webp`); + expect(res.headers.location).toContain('static=1'); + }); + + test('GET /files/:key webpublic svg は mediaProxy/svg.webp にリダイレクトする', async () => { + const accessKey = randomString(); + const webpublicKey = randomString(); + await insertDriveFile({ + accessKey, + webpublicAccessKey: webpublicKey, + storedInternal: false, + isLink: true, + uri: remoteSvgUrl, + name: 'vector.svg', + type: 'image/svg+xml', + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/files/${webpublicKey}`, + }); + + expect(res.statusCode).toBe(301); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers.location).toContain(`${config.mediaProxy}/svg.webp`); + }); + }); + + describe('GET /files/:key/*', () => { + test('GET /files/:key/* 正規の /files/:key にリダイレクトする', async () => { + const res = await fastify.inject({ + method: 'GET', + url: '/files/testkey/extra/path', + }); + + expect(res.statusCode).toBe(301); + expect(res.headers.location).toBe(`${config.url}/files/testkey`); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + }); + }); + + describe('GET /proxy/:url*', () => { + test('GET /proxy/:url* 外部メディアプロキシへリダイレクトする', async () => { + const res = await externalFastify.inject({ + method: 'GET', + url: '/proxy/path-part?url=https%3A%2F%2Fexample.com%2Fimg.png&static=1', + }); + + expect(res.statusCode).toBe(301); + expect(res.headers['cache-control']).toBe('public, max-age=259200'); + expect(res.headers.location).toContain('https://media-proxy.test/'); + expect(res.headers.location).toContain('url=https%3A%2F%2Fexample.com%2Fimg.png'); + expect(res.headers.location).toContain('static=1'); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + }); + + test('GET /proxy/:url* misskey User-Agent を拒否する', async () => { + const res = await fastify.inject({ + method: 'GET', + url: '/proxy/any?url=https%3A%2F%2Fexample.com%2Fimg.png', + headers: { + 'user-agent': 'misskey/1.0', + }, + }); + + expect(res.statusCode).toBe(403); + expect(res.headers['cache-control']).toBe('max-age=300'); + }); + + test('GET /proxy/:url* origin 指定時は User-Agent 必須を検証する', async () => { + const res = await fastify.inject({ + method: 'GET', + url: '/proxy/any?url=https%3A%2F%2Fexample.com%2Fimg.png&origin=1', + headers: { + 'user-agent': '', + }, + }); + + expect(res.statusCode).toBe(400); + expect(res.headers['cache-control']).toBe('max-age=300'); + expect(res.headers.location).toBeUndefined(); + expect(res.headers['content-security-policy']).toBe('default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + }); + + test('GET /proxy/:url* emoji 指定で非画像は 404 を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remoteTextUrl)}&emoji=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(404); + expect(res.headers['cache-control']).toBe('max-age=300'); + }); + + test('GET /proxy/:url* 非画像は 403 を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remoteTextUrl)}`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(403); + expect(res.headers['cache-control']).toBe('max-age=300'); + }); + + test('GET /proxy/:url* emoji static で webp を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remotePngUrl)}&emoji=1&static=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/webp'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.png.webp'); + }); + + test('GET /proxy/:url* avatar static で webp を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remotePngUrl)}&avatar=1&static=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/webp'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.png.webp'); + }); + + test('GET /proxy/:url* static で webp を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remotePngUrl)}&static=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/webp'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.png.webp'); + }); + + test('GET /proxy/:url* preview で webp を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remotePngUrl)}&preview=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/webp'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.png.webp'); + }); + + test('GET /proxy/:url* svg を webp に変換する', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remoteSvgUrl)}`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/webp'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.svg.webp'); + }); + + test('GET /proxy/:url* badge で低エントロピー画像は 404 を返す', async () => { + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(remoteFlatPngUrl)}&badge=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(404); + expect(res.headers['cache-control']).toBe('max-age=300'); + }); + + test('GET /proxy/:url* 画像をそのまま返す', async () => { + const accessKey = randomString(); + writeInternalFile(accessKey); + await insertDriveFile({ + accessKey, + storedInternal: true, + isLink: false, + }); + + const res = await fastify.inject({ + method: 'GET', + url: `/proxy/any?url=${encodeURIComponent(`${config.url}/files/${accessKey}`)}&origin=1`, + headers: { + 'user-agent': 'Mozilla/5.0', + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('image/png'); + expect(res.headers['cache-control']).toBe('max-age=31536000, immutable'); + expect(res.headers['content-disposition'] ?? '').toContain('dummy.png'); + }); + }); +}); diff --git a/packages/backend/test/unit/server/api/drive/files/create.ts b/packages/backend/test/unit/server/api/drive/files/create.ts new file mode 100644 index 0000000000..e86b818ca5 --- /dev/null +++ b/packages/backend/test/unit/server/api/drive/files/create.ts @@ -0,0 +1,216 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { FastifyInstance } from 'fastify'; +import request from 'supertest'; +import { randomString } from '../../../../../utils.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { RoleService } from '@/core/RoleService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { DriveFoldersRepository, MiDriveFolder, MiRole, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { MiUser } from '@/models/User.js'; +import { ServerModule } from '@/server/ServerModule.js'; +import { ServerService } from '@/server/ServerService.js'; +import { IdService } from '@/core/IdService.js'; + +// TODO: uploadableFileTypes で許可されていないファイルが弾かれるかのテスト + +describe('/drive/files/create', () => { + let module: TestingModule; + let server: FastifyInstance; + let roleService: RoleService; + let idService: IdService; + + let root: MiUser; + let role_tinyAttachment: MiRole; + let role_imageOnly: MiRole; + let role_allowAllTypes: MiRole; + + let folder: MiDriveFolder; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule, ServerModule], + }).compile(); + module.enableShutdownHooks(); + + const serverService = module.get(ServerService); + await serverService.launch(); + server = serverService.fastify; + + idService = module.get(IdService); + + const usersRepository = module.get(DI.usersRepository); + await usersRepository.createQueryBuilder().delete().execute(); + root = await usersRepository.insert({ + id: idService.gen(), + username: 'root', + usernameLower: 'root', + token: '1234567890123456', + }).then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + const userProfilesRepository = module.get(DI.userProfilesRepository); + await userProfilesRepository.createQueryBuilder().delete().execute(); + await userProfilesRepository.insert({ + userId: root.id, + }); + + const driveFoldersRepository = module.get(DI.driveFoldersRepository); + folder = await driveFoldersRepository.insertOne({ + id: idService.gen(), + name: 'root-folder', + parentId: null, + userId: root.id, + }); + + roleService = module.get(RoleService); + role_imageOnly = await roleService.create({ + name: 'test-role001', + description: 'Test role001 description', + target: 'manual', + policies: { + uploadableFileTypes: { + useDefault: false, + priority: 1, + value: ['image/png'], + }, + }, + }); + role_allowAllTypes = await roleService.create({ + name: 'test-role002', + description: 'Test role002 description', + target: 'manual', + policies: { + uploadableFileTypes: { + useDefault: false, + priority: 1, + value: ['*/*'], + }, + }, + }); + role_tinyAttachment = await roleService.create({ + name: 'test-role003', + description: 'Test role003 description', + target: 'manual', + policies: { + maxFileSizeMb: { + useDefault: false, + priority: 1, + // 10byte + value: 10 / 1024 / 1024, + }, + }, + }); + }); + + beforeEach(async () => { + await roleService.unassign(root.id, role_tinyAttachment.id).catch(() => { + }); + await roleService.unassign(root.id, role_imageOnly.id).catch(() => { + }); + await roleService.unassign(root.id, role_allowAllTypes.id).catch(() => { + }); + }); + + afterAll(async () => { + await server.close(); + await module.close(); + }); + + async function postFile(props: { + name: string, + comment: string, + isSensitive: boolean, + force: boolean, + fileContent: Buffer | string, + }) { + const { name, comment, isSensitive, force, fileContent } = props; + + return await request(server.server) + .post('/api/drive/files/create') + .set('Content-Type', 'multipart/form-data') + .attach('file', fileContent) + .field('name', name) + .field('comment', comment) + .field('isSensitive', isSensitive) + .field('force', force) + .field('folderId', folder.id) + .field('i', root.token ?? ''); + } + + test('200 ok (all types allowed)', async () => { + await roleService.assign(root.id, role_allowAllTypes.id); + + const name = randomString(); + const comment = randomString(); + const result = await postFile({ + name: name, + comment: comment, + isSensitive: true, + force: true, + fileContent: Buffer.from('a'.repeat(1000 * 1000)), + }); + expect(result.statusCode).toBe(200); + expect(result.body.name).toBe(name + '.unknown'); + expect(result.body.comment).toBe(comment); + expect(result.body.isSensitive).toBe(true); + expect(result.body.folderId).toBe(folder.id); + }); + + test('400 when not allowed type', async () => { + await roleService.assign(root.id, role_imageOnly.id); + + const name = randomString(); + const comment = randomString(); + const result = await postFile({ + name: name, + comment: comment, + isSensitive: true, + force: true, + fileContent: Buffer.from('a'.repeat(10)), + }); + expect(result.statusCode).toBe(400); + expect(result.body.error.code).toBe('UNALLOWED_FILE_TYPE'); + }); + + test('200 ok (with size limited role)', async () => { + await roleService.assign(root.id, role_allowAllTypes.id); + await roleService.assign(root.id, role_tinyAttachment.id); + + const name = randomString(); + const comment = randomString(); + const result = await postFile({ + name: name, + comment: comment, + isSensitive: true, + force: true, + fileContent: Buffer.from('a'.repeat(10)), + }); + expect(result.statusCode).toBe(200); + expect(result.body.name).toBe(name + '.unknown'); + expect(result.body.comment).toBe(comment); + expect(result.body.isSensitive).toBe(true); + expect(result.body.folderId).toBe(folder.id); + }); + + test('413 too large', async () => { + await roleService.assign(root.id, role_allowAllTypes.id); + await roleService.assign(root.id, role_tinyAttachment.id); + + const name = randomString(); + const comment = randomString(); + const result = await postFile({ + name: name, + comment: comment, + isSensitive: true, + force: true, + fileContent: Buffer.from('a'.repeat(11)), + }); + expect(result.statusCode).toBe(413); + expect(result.body.error.code).toBe('MAX_FILE_SIZE_EXCEEDED'); + }); +}); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 7eecf8bb0d..f91fb7f9b1 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -10,8 +10,8 @@ import { randomUUID } from 'node:crypto'; import { inspect } from 'node:util'; import WebSocket, { ClientOptions } from 'ws'; import fetch, { File, RequestInit, type Headers } from 'node-fetch'; +import * as htmlParser from 'node-html-parser'; import { DataSource } from 'typeorm'; -import { JSDOM } from 'jsdom'; import { type Response } from 'node-fetch'; import Fastify from 'fastify'; import { entities } from '../src/postgres.js'; @@ -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([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); @@ -400,37 +404,28 @@ export function connectStream(user: UserToken, } export const waitFire = async (user: UserToken, channel: C, trgr: () => any, cond: (msg: Record) => boolean, params?: misskey.Channels[C]['params']) => { - return new Promise(async (res, rej) => { - let timer: NodeJS.Timeout | null = null; + let ws: WebSocket | undefined; - let ws: WebSocket; - try { - ws = await connectStream(user, channel, msg => { + try { + let callback: (msg: Record) => void; + const receivedPromise = new Promise((resolve) => { + callback = (msg: Record) => { if (cond(msg)) { - ws.close(); - if (timer) clearTimeout(timer); - res(true); + resolve(true); } - }, params); - } catch (e) { - rej(e); - } + }; + }); - if (!ws!) return; + ws = await connectStream(user, channel, callback!, params); + await trgr(); - timer = setTimeout(() => { - ws.close(); - res(false); - }, 3000); - - try { - await trgr(); - } catch (e) { - ws.close(); - if (timer) clearTimeout(timer); - rej(e); - } - }); + return await Promise.race([ + receivedPromise, + new Promise((r) => setTimeout(() => r(), 3000)).then(() => false), + ]); + } finally { + if (ws) ws.close(); + } }; /** @@ -464,7 +459,7 @@ export function makeStreamCatcher( export type SimpleGetResponse = { status: number, - body: any | JSDOM | null, + body: any | null, type: string | null, location: string | null }; @@ -495,7 +490,7 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde const body = jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() : - htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) : + htmlTypes.includes(res.headers.get('content-type') ?? '') ? htmlParser.parse(await res.text()) : await bodyExtractor(res); return { @@ -608,8 +603,8 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) { username: config.db.user, password: config.db.pass, database: config.db.db, - synchronize: true && !justBorrow, - dropSchema: true && !justBorrow, + synchronize: !justBorrow, + dropSchema: !justBorrow, entities: initEntities ?? entities, }); @@ -661,7 +656,9 @@ export async function captureWebhook(postAction: () => let timeoutHandle: NodeJS.Timeout | null = null; const result = await new Promise(async (resolve, reject) => { fastify.all('/', async (req, res) => { - timeoutHandle && clearTimeout(timeoutHandle); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } const body = JSON.stringify(req.body); res.status(200).send('ok'); diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 2b15a5cc7a..dac56f25de 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -23,12 +23,16 @@ "emitDecoratorMetadata": true, "resolveJsonModule": true, "isolatedModules": true, + "jsx": "react-jsx", + "jsxImportSource": "@kitajs/html", "rootDir": "./src", - "baseUrl": "./", "paths": { "@/*": ["./src/*"] }, "outDir": "./built", + "plugins": [ + {"name": "@kitajs/ts-html-plugin"} + ], "types": [ "node" ], @@ -43,7 +47,8 @@ }, "compileOnSave": false, "include": [ - "./src/**/*.ts" + "./src/**/*.ts", + "./src/**/*.tsx" ], "exclude": [ "./src/**/*.test.ts" diff --git a/packages/frontend-builder/README.txt b/packages/frontend-builder/README.txt new file mode 100644 index 0000000000..428166e792 --- /dev/null +++ b/packages/frontend-builder/README.txt @@ -0,0 +1 @@ +This package contains the common scripts that are used to build the frontend and frontend-embed packages. diff --git a/packages/frontend-builder/eslint.config.js b/packages/frontend-builder/eslint.config.js new file mode 100644 index 0000000000..7337abe14c --- /dev/null +++ b/packages/frontend-builder/eslint.config.js @@ -0,0 +1,51 @@ +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import sharedConfig from '../shared/eslint.config.js'; + +// eslint-disable-next-line import/no-default-export +export default [ + ...sharedConfig, + { + files: [ + '**/*.ts', + ], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])), + ...globals.browser, + + // Node.js + module: false, + require: false, + __dirname: false, + + // Misskey + _DEV_: false, + _LANGS_: false, + _VERSION_: false, + _ENV_: false, + _PERF_PREFIX_: false, + }, + parserOptions: { + parser: tsParser, + project: ['./tsconfig.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-empty-interface': ['error', { + allowSingleExtends: true, + }], + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + 'id-denylist': ['error', 'window', 'e'], + 'no-shadow': ['warn'], + }, + }, + { + ignores: [ + ], + }, +]; diff --git a/packages/frontend-builder/locale-inliner.ts b/packages/frontend-builder/locale-inliner.ts new file mode 100644 index 0000000000..191d7250a6 --- /dev/null +++ b/packages/frontend-builder/locale-inliner.ts @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'fs/promises'; +import * as path from 'node:path'; +import MagicString from 'magic-string'; +import { collectModifications } from './locale-inliner/collect-modifications.js'; +import { applyWithLocale } from './locale-inliner/apply-with-locale.js'; +import { blankLogger } from './logger.js'; +import type { Logger } from './logger.js'; +import type { Locale } from 'i18n'; +import type { Manifest as ViteManifest } from 'vite'; + +export class LocaleInliner { + outputDir: string; + scriptsDir: string; + i18nFile: string; + i18nFileName: string; + logger: Logger; + chunks: ScriptChunk[]; + + static async create(options: { + outputDir: string, + scriptsDir: string, + i18nFile: string, + logger: Logger, + }): Promise { + const manifest: ViteManifest = JSON.parse(await fs.readFile(`${options.outputDir}/manifest.json`, 'utf-8')); + return new LocaleInliner({ ...options, manifest }); + } + + constructor(options: { + outputDir: string, + scriptsDir: string, + i18nFile: string, + manifest: ViteManifest, + logger: Logger, + }) { + this.outputDir = options.outputDir; + this.scriptsDir = options.scriptsDir; + this.i18nFile = options.i18nFile; + this.i18nFileName = this.stripScriptDir(options.manifest[this.i18nFile].file); + this.logger = options.logger; + this.chunks = Object.values(options.manifest).filter(chunk => this.isScriptFile(chunk.file)).map(chunk => ({ + fileName: this.stripScriptDir(chunk.file), + chunkName: chunk.name, + })); + } + + async loadFiles() { + await Promise.all(this.chunks.map(async chunk => { + const filePath = path.join(this.outputDir, this.scriptsDir, chunk.fileName); + chunk.sourceCode = await fs.readFile(filePath, 'utf-8'); + })); + } + + collectsModifications() { + for (const chunk of this.chunks) { + if (!chunk.sourceCode) { + throw new Error(`Source code for ${chunk.fileName} is not loaded.`); + } + const fileLogger = this.logger.prefixed(`${chunk.fileName} (${chunk.chunkName}): `); + chunk.modifications = collectModifications(chunk.sourceCode, chunk.fileName, fileLogger, this); + } + } + + async saveAllLocales(locales: Record) { + const localeNames = Object.keys(locales); + for (const localeName of localeNames) { + this.logger.info(`Creating bundle for ${localeName}`); + await this.saveLocale(localeName, locales[localeName]); + } + this.logger.info('Done'); + } + + async saveLocale(localeName: string, localeJson: Locale) { + // create directory + await fs.mkdir(path.join(this.outputDir, localeName), { recursive: true }); + const localeLogger = localeName === 'ja-JP' ? this.logger : blankLogger; // we want to log for single locale only + for (const chunk of this.chunks) { + if (!chunk.sourceCode || !chunk.modifications) { + throw new Error(`Source code or modifications for ${chunk.fileName} is not available.`); + } + const fileLogger = localeLogger.prefixed(`${chunk.fileName} (${chunk.chunkName}): `); + const magicString = new MagicString(chunk.sourceCode); + applyWithLocale(magicString, chunk.modifications, localeName, localeJson, fileLogger); + + await fs.writeFile(path.join(this.outputDir, localeName, chunk.fileName), magicString.toString()); + } + } + + isScriptFile(fileName: string) { + return fileName.startsWith(this.scriptsDir + '/') && fileName.endsWith('.js'); + } + + stripScriptDir(fileName: string) { + if (!fileName.startsWith(this.scriptsDir + '/')) { + throw new Error(`${fileName} does not start with ${this.scriptsDir}/`); + } + return fileName.slice(this.scriptsDir.length + 1); + } +} + +interface ScriptChunk { + fileName: string; + chunkName?: string; + sourceCode?: string; + modifications?: TextModification[]; +} + +export type TextModification = { + type: 'delete'; + begin: number; + end: number; + localizedOnly: boolean; +} | { + // can be used later to insert '../scripts' for common files + type: 'insert'; + begin: number; + text: string; + localizedOnly: boolean; +} | { + type: 'replace'; + begin: number; + end: number; + text: string; + localizedOnly: boolean; +} | { + type: 'localized'; + begin: number; + end: number; + localizationKey: string[]; + localizedOnly: true; +} | { + type: 'parameterized-function'; + begin: number; + end: number; + localizationKey: string[]; + localizedOnly: true; +} | { + type: 'locale-name'; + begin: number; + end: number; + literal: boolean; + localizedOnly: true; +} | { + type: 'locale-json'; + begin: number; + end: number; + localizedOnly: true; +}; diff --git a/packages/frontend-builder/locale-inliner/apply-with-locale.ts b/packages/frontend-builder/locale-inliner/apply-with-locale.ts new file mode 100644 index 0000000000..78851d3029 --- /dev/null +++ b/packages/frontend-builder/locale-inliner/apply-with-locale.ts @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MagicString from 'magic-string'; +import { assertNever } from '../utils.js'; +import type { ILocale, Locale } from 'i18n'; +import type { TextModification } from '../locale-inliner.js'; +import type { Logger } from '../logger.js'; + +export function applyWithLocale( + sourceCode: MagicString, + modifications: TextModification[], + localeName: string, + localeJson: Locale, + fileLogger: Logger, +) { + for (const modification of modifications) { + switch (modification.type) { + case 'delete': + sourceCode.remove(modification.begin, modification.end); + break; + case 'insert': + sourceCode.appendRight(modification.begin, modification.text); + break; + case 'replace': + sourceCode.update(modification.begin, modification.end, modification.text); + break; + case 'localized': { + const accessed = getPropertyByPath(localeJson, modification.localizationKey); + if (accessed == null) { + fileLogger.warn(`Cannot find localization key ${modification.localizationKey.join('.')}`); + } + sourceCode.update(modification.begin, modification.end, JSON.stringify(accessed)); + break; + } + case 'parameterized-function': { + const accessed = getPropertyByPath(localeJson, modification.localizationKey); + let replacement: string; + if (typeof accessed === 'string') { + replacement = formatFunction(accessed); + } else if (typeof accessed === 'object' && accessed !== null) { + replacement = `({${Object.entries(accessed).map(([key, value]) => `${JSON.stringify(key)}:${formatFunction(value)}`).join(',')}})`; + } else { + fileLogger.warn(`Cannot find localization key ${modification.localizationKey.join('.')}`); + replacement = '(() => "")'; // placeholder for missing locale + } + sourceCode.update(modification.begin, modification.end, replacement); + break; + + function formatFunction(format: string): string { + const params = new Set(); + const components: string[] = []; + let lastIndex = 0; + for (const match of format.matchAll(/\{(.+?)}/g)) { + const [fullMatch, paramName] = match; + if (lastIndex < match.index) { + components.push(JSON.stringify(format.slice(lastIndex, match.index))); + } + params.add(paramName); + components.push(paramName); + lastIndex = match.index + fullMatch.length; + } + components.push(JSON.stringify(format.slice(lastIndex))); + + // we replace with `(({name,count})=>(name+count+"some"))` + const paramList = Array.from(params).join(','); + let body = components.filter(x => x !== '""').join('+'); + if (body === '') body = '""'; // if the body is empty, we return empty string + return `(({${paramList}})=>(${body}))`; + } + } + case 'locale-name': { + sourceCode.update(modification.begin, modification.end, modification.literal ? JSON.stringify(localeName) : localeName); + break; + } + case 'locale-json': { + // locale-json is inlined to place where initialize module-level variable which is executed only once. + // In such case we can use JSON.parse to speed up the parsing script. + // https://v8.dev/blog/cost-of-javascript-2019#json + sourceCode.update(modification.begin, modification.end, `JSON.parse(${JSON.stringify(JSON.stringify(localeJson))})`); + break; + } + default: { + assertNever(modification); + } + } + } +} + +function getPropertyByPath(localeJson: ILocale, localizationKey: string[]): string | object | null { + if (localizationKey.length === 0) return localeJson; + let current: ILocale | string = localeJson; + for (const key of localizationKey) { + if (typeof current !== 'object' || !(key in current)) { + return null; // Key not found + } + current = current[key]; + } + return current; +} diff --git a/packages/frontend-builder/locale-inliner/collect-modifications.ts b/packages/frontend-builder/locale-inliner/collect-modifications.ts new file mode 100644 index 0000000000..59e5d96517 --- /dev/null +++ b/packages/frontend-builder/locale-inliner/collect-modifications.ts @@ -0,0 +1,425 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { parseAst } from 'vite'; +import * as estreeWalker from 'estree-walker'; +import { assertNever, assertType } from '../utils.js'; +import type { AstNode, ProgramNode } from 'rollup'; +import type * as estree from 'estree'; +import type { LocaleInliner, TextModification } from '../locale-inliner.js'; +import type { Logger } from '../logger.js'; + +// WalkerContext is not exported from estree-walker, so we define it here +interface WalkerContext { + skip: () => void; +} + +export function collectModifications(sourceCode: string, fileName: string, fileLogger: Logger, inliner: LocaleInliner): TextModification[] { + let programNode: ProgramNode; + try { + programNode = parseAst(sourceCode); + } catch (err) { + fileLogger.error(`Failed to parse source code: ${err}`); + return []; + } + if (programNode.sourceType !== 'module') { + fileLogger.error('Source code is not a module.'); + return []; + } + + const modifications: TextModification[] = []; + + // first + // 1) replace all `scripts/` path literals with locale code + // 2) replace all `localStorage.getItem("lang")` with `localeName` variable + // 3) replace all `await window.fetch(`/assets/locales/${d}.${x}.json`).then(u=>u.json())` with `localeJson` variable + estreeWalker.walk(programNode, { + enter(this: WalkerContext, node: Node) { + assertType(node); + + if (node.type === 'Literal' && typeof node.value === 'string' && node.raw) { + if (node.raw.substring(1).startsWith(inliner.scriptsDir)) { + // we find `scripts/\w+\.js` literal and replace 'scripts' part with locale code + fileLogger.debug(`${lineCol(sourceCode, node)}: found ${inliner.scriptsDir}/ path literal ${node.raw}`); + modifications.push({ + type: 'locale-name', + begin: node.start + 1, + end: node.start + 1 + inliner.scriptsDir.length, + literal: false, + localizedOnly: true, + }); + } + if (node.raw.substring(1, node.raw.length - 1) === `${inliner.scriptsDir}/${inliner.i18nFileName}`) { + // we find `scripts/i18n.ts` literal. + // This is tipically in depmap and replace with this file name to avoid unnecessary loading i18n script + fileLogger.debug(`${lineCol(sourceCode, node)}: found ${inliner.i18nFileName} path literal ${node.raw}`); + modifications.push({ + type: 'replace', + begin: node.end - 1 - inliner.i18nFileName.length, + end: node.end - 1, + text: fileName, + localizedOnly: true, + }); + } + } + + if (isLocalStorageGetItemLang(node)) { + fileLogger.debug(`${lineCol(sourceCode, node)}: found localStorage.getItem("lang") call`); + modifications.push({ + type: 'locale-name', + begin: node.start, + end: node.end, + literal: true, + localizedOnly: true, + }); + } + + if (isAwaitFetchLocaleThenJson(node)) { + // await window.fetch(`/assets/locales/${d}.${x}.json`).then(u=>u.json(), () => null) + fileLogger.debug(`${lineCol(sourceCode, node)}: found await window.fetch(\`/assets/locales/\${d}.\${x}.json\`).then(u=>u.json()) call`); + modifications.push({ + type: 'locale-json', + begin: node.start, + end: node.end, + localizedOnly: true, + }); + } + }, + }); + + const importSpecifierResult = findImportSpecifier(programNode, inliner.i18nFileName, 'i18n'); + + switch (importSpecifierResult.type) { + case 'no-import': + fileLogger.debug('No import of i18n found, skipping inlining.'); + return modifications; + case 'no-specifiers': + fileLogger.debug('Importing i18n without specifiers, removing the import.'); + modifications.push({ + type: 'delete', + begin: importSpecifierResult.importNode.start, + end: importSpecifierResult.importNode.end, + localizedOnly: false, + }); + return modifications; + case 'unexpected-specifiers': + fileLogger.info(`Importing ${inliner.i18nFileName} found but with unexpected specifiers. Skipping inlining.`); + return modifications; + case 'specifier': + fileLogger.debug(`Found import i18n as ${importSpecifierResult.localI18nIdentifier}`); + break; + } + + const i18nImport = importSpecifierResult.importNode; + const localI18nIdentifier = importSpecifierResult.localI18nIdentifier; + + // Check if the identifier is already declared in the file. + // If it is, we may overwrite it and cause issues so we skip inlining + let isSupported = true; + estreeWalker.walk(programNode, { + enter(node) { + if (node.type === 'VariableDeclaration') { + assertType(node); + for (const id of node.declarations.flatMap(x => declsOfPattern(x.id))) { + if (id === localI18nIdentifier) { + isSupported = false; + } + } + } + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!isSupported) { + fileLogger.error(`Duplicated identifier "${localI18nIdentifier}" in variable declaration. Skipping inlining.`); + return modifications; + } + + fileLogger.debug(`imports i18n as ${localI18nIdentifier}`); + + // In case of substitution failure, we will preserve the import statement + // otherwise we will remove it. + let preserveI18nImport = false; + + const toSkip = new Set(); + toSkip.add(i18nImport); + estreeWalker.walk(programNode, { + enter(this: WalkerContext, node, parent, property) { + assertType(node); + assertType(parent); + if (toSkip.has(node)) { + // This is the import specifier, skip processing it + this.skip(); + return; + } + + // We don't care original name part of the import declaration + if (node.type === 'ImportDeclaration') this.skip(); + + if (node.type === 'Identifier') { + assertType(node); + assertType(parent); + if (parent.type === 'Property' && !parent.computed && property === 'key') return; // we don't care 'id' part of { id: expr } + if (parent.type === 'MemberExpression' && !parent.computed && property === 'property') return; // we don't care 'id' part of { id: expr } + if (parent.type === 'ExportSpecifier' && property === 'exported') return; // we don't care 'id' part of { id: expr } + if (node.name === localI18nIdentifier) { + fileLogger.error(`${lineCol(sourceCode, node)}: Using i18n identifier "${localI18nIdentifier}" directly. Skipping inlining.`); + preserveI18nImport = true; + } + } else if (node.type === 'MemberExpression') { + assertType(node); + const i18nPath = parseI18nPropertyAccess(node); + if (i18nPath != null && i18nPath.length >= 2 && i18nPath[0] === 'ts') { + if (parent.type === 'CallExpression' && property === 'callee') return; // we don't want to process `i18n.ts.property.stringBuiltinMethod()` + if (i18nPath.at(-1)?.startsWith('_')) fileLogger.debug(`found i18n grouped property access ${i18nPath.join('.')}`); + else fileLogger.debug(`${lineCol(sourceCode, node)}: found i18n property access ${i18nPath.join('.')}`); + // it's i18n.ts.propertyAccess + // i18n.ts.* will always be resolved to string or object containing strings + modifications.push({ + type: 'localized', + begin: node.start, + end: node.end, + localizationKey: i18nPath.slice(1), // remove 'ts' prefix + localizedOnly: true, + }); + this.skip(); + } else if (i18nPath != null && i18nPath.length >= 2 && i18nPath[0] === 'tsx') { + // it's parameterized locale substitution (`i18n.tsx.property(parameters)`) + // we expect the parameter to be an object literal + fileLogger.debug(`${lineCol(sourceCode, node)}: found i18n function access (object) ${i18nPath.join('.')}`); + modifications.push({ + type: 'parameterized-function', + begin: node.start, + end: node.end, + localizationKey: i18nPath.slice(1), // remove 'tsx' prefix + localizedOnly: true, + }); + this.skip(); + } + } else if (node.type === 'ArrowFunctionExpression') { + assertType(node); + // If there is 'i18n' in the parameters, we care interior of the function + if (node.params.flatMap(param => declsOfPattern(param)).includes(localI18nIdentifier)) this.skip(); + } + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!preserveI18nImport) { + fileLogger.debug('removing i18n import statement'); + modifications.push({ + type: 'delete', + begin: i18nImport.start, + end: i18nImport.end, + localizedOnly: true, + }); + } + + function parseI18nPropertyAccess(node: estree.Expression | estree.Super): string[] | null { + if (node.type === 'Identifier' && node.name === localI18nIdentifier) return []; // i18n itself + if (node.type !== 'MemberExpression') return null; + // super.* + if (node.object.type === 'Super') return null; + + // i18n?.property is not supported + if (node.optional) return null; + + let id: string | null = null; + if (node.computed) { + if (node.property.type === 'Literal' && typeof node.property.value === 'string') { + id = node.property.value; + } + } else { + if (node.property.type === 'Identifier') { + id = node.property.name; + } + } + // non-constant property access + if (id == null) return null; + + const parentAccess = parseI18nPropertyAccess(node.object); + if (parentAccess == null) return null; + return [...parentAccess, id]; + } + + return modifications; +} + +function declsOfPattern(pattern: estree.Pattern | null): string[] { + if (pattern == null) return []; + switch (pattern.type) { + case 'Identifier': + return [pattern.name]; + case 'ObjectPattern': + return pattern.properties.flatMap(prop => { + switch (prop.type) { + case 'Property': + return declsOfPattern(prop.value); + case 'RestElement': + return declsOfPattern(prop.argument); + default: + assertNever(prop); + } + }); + case 'ArrayPattern': + return pattern.elements.flatMap(p => declsOfPattern(p)); + case 'RestElement': + return declsOfPattern(pattern.argument); + case 'AssignmentPattern': + return declsOfPattern(pattern.left); + case 'MemberExpression': + // assignment pattern so no new variable is declared + return []; + default: + assertNever(pattern); + } +} + +function lineCol(sourceCode: string, node: estree.Node): string { + assertType(node); + const leading = sourceCode.slice(0, node.start); + const lines = leading.split('\n'); + const line = lines.length; + const col = lines[lines.length - 1].length + 1; // +1 for 1-based index + return `(${line}:${col})`; +} + +//region checker functions + +type Node = + | estree.AssignmentProperty + | estree.CatchClause + | estree.Class + | estree.ClassBody + | estree.Expression + | estree.Function + | estree.Identifier + | estree.Literal + | estree.MethodDefinition + | estree.ModuleDeclaration + | estree.ModuleSpecifier + | estree.Pattern + | estree.PrivateIdentifier + | estree.Program + | estree.Property + | estree.PropertyDefinition + | estree.SpreadElement + | estree.Statement + | estree.Super + | estree.SwitchCase + | estree.TemplateElement + | estree.VariableDeclarator + ; + +// localStorage.getItem("lang") +function isLocalStorageGetItemLang(getItemCall: Node): boolean { + if (getItemCall.type !== 'CallExpression') return false; + if (getItemCall.arguments.length !== 1) return false; + + const langLiteral = getItemCall.arguments[0]; + if (!isStringLiteral(langLiteral, 'lang')) return false; + + const getItemFunction = getItemCall.callee; + if (!isMemberExpression(getItemFunction, 'getItem')) return false; + + const localStorageObject = getItemFunction.object; + if (!isIdentifier(localStorageObject, 'localStorage')) return false; + + return true; +} + +// await window.fetch(`/assets/locales/${d}.${x}.json`).then(u => u.json(), ....) +function isAwaitFetchLocaleThenJson(awaitNode: Node): boolean { + if (awaitNode.type !== 'AwaitExpression') return false; + + const thenCall = awaitNode.argument; + if (thenCall.type !== 'CallExpression') return false; + if (thenCall.arguments.length < 1) return false; + + const arrowFunction = thenCall.arguments[0]; + if (arrowFunction.type !== 'ArrowFunctionExpression') return false; + if (arrowFunction.params.length !== 1) return false; + + const arrowBodyCall = arrowFunction.body; + if (arrowBodyCall.type !== 'CallExpression') return false; + + const jsonFunction = arrowBodyCall.callee; + if (!isMemberExpression(jsonFunction, 'json')) return false; + + const thenFunction = thenCall.callee; + if (!isMemberExpression(thenFunction, 'then')) return false; + + const fetchCall = thenFunction.object; + if (fetchCall.type !== 'CallExpression') return false; + if (fetchCall.arguments.length !== 1) return false; + + // `/assets/locales/${d}.${x}.json` + const assetLocaleTemplate = fetchCall.arguments[0]; + if (assetLocaleTemplate.type !== 'TemplateLiteral') return false; + if (assetLocaleTemplate.quasis.length !== 3) return false; + if (assetLocaleTemplate.expressions.length !== 2) return false; + if (assetLocaleTemplate.quasis[0].value.cooked !== '/assets/locales/') return false; + if (assetLocaleTemplate.quasis[1].value.cooked !== '.') return false; + if (assetLocaleTemplate.quasis[2].value.cooked !== '.json') return false; + + const fetchFunction = fetchCall.callee; + if (!isMemberExpression(fetchFunction, 'fetch')) return false; + const windowObject = fetchFunction.object; + if (!isIdentifier(windowObject, 'window')) return false; + + return true; +} + +type SpecifierResult = + | { type: 'no-import' } + | { type: 'no-specifiers', importNode: estree.ImportDeclaration & AstNode } + | { type: 'unexpected-specifiers', importNode: estree.ImportDeclaration & AstNode } + | { type: 'specifier', localI18nIdentifier: string, importNode: estree.ImportDeclaration & AstNode } + ; + +function findImportSpecifier(programNode: ProgramNode, i18nFileName: string, i18nSymbol: string): SpecifierResult { + const imports = programNode.body.filter(x => x.type === 'ImportDeclaration'); + const importNode = imports.find(x => x.source.value === `./${i18nFileName}`) as estree.ImportDeclaration | undefined; + if (!importNode) return { type: 'no-import' }; + assertType(importNode); + + if (importNode.specifiers.length === 0) { + return { type: 'no-specifiers', importNode }; + } + + if (importNode.specifiers.length !== 1) { + return { type: 'unexpected-specifiers', importNode }; + } + const i18nImportSpecifier = importNode.specifiers[0]; + if (i18nImportSpecifier.type !== 'ImportSpecifier') { + return { type: 'unexpected-specifiers', importNode }; + } + + if (i18nImportSpecifier.imported.type !== 'Identifier') { + return { type: 'unexpected-specifiers', importNode }; + } + + const importingIdentifier = i18nImportSpecifier.imported.name; + if (importingIdentifier !== i18nSymbol) { + return { type: 'unexpected-specifiers', importNode }; + } + const localI18nIdentifier = i18nImportSpecifier.local.name; + return { type: 'specifier', localI18nIdentifier, importNode }; +} + +// checker helpers +function isMemberExpression(node: Node, property: string): node is estree.MemberExpression { + return node.type === 'MemberExpression' && !node.computed && node.property.type === 'Identifier' && node.property.name === property; +} + +function isStringLiteral(node: Node, value: string): node is estree.Literal { + return node.type === 'Literal' && typeof node.value === 'string' && node.value === value; +} + +function isIdentifier(node: Node, name: string): node is estree.Identifier { + return node.type === 'Identifier' && node.name === name; +} + +//endregion diff --git a/packages/frontend-builder/logger.ts b/packages/frontend-builder/logger.ts new file mode 100644 index 0000000000..c619882380 --- /dev/null +++ b/packages/frontend-builder/logger.ts @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as process from 'node:process'; + +const debug = process.env.BUILDER_DEBUG !== undefined && process.env.BUILDER_DEBUG !== '0'; + +export interface Logger { + debug(message: string): void; + + warn(message: string): void; + + error(message: string): void; + + info(message: string): void; + + prefixed(newPrefix: string): Logger; +} + +interface RootLogger extends Logger { + warningCount: number; + errorCount: number; +} + +export function createLogger(): RootLogger { + return loggerFactory('', { + warningCount: 0, + errorCount: 0, + }); +} + +type LogContext = { + warningCount: number; + errorCount: number; +}; + +function loggerFactory(prefix: string, context: LogContext): RootLogger { + return { + debug: (message: string) => { + if (debug) console.log(`[DBG] ${prefix}${message}`); + }, + warn: (message: string) => { + context.warningCount++; + console.log(`${debug ? '[WRN]' : 'w:'} ${prefix}${message}`); + }, + error: (message: string) => { + context.errorCount++; + console.error(`${debug ? '[ERR]' : 'e:'} ${prefix}${message}`); + }, + info: (message: string) => { + console.error(`${debug ? '[INF]' : 'i:'} ${prefix}${message}`); + }, + prefixed: (newPrefix: string) => { + return loggerFactory(`${prefix}${newPrefix}`, context); + }, + get warningCount() { + return context.warningCount; + }, + get errorCount() { + return context.errorCount; + }, + }; +} + +export const blankLogger: Logger = { + debug: () => void 0, + warn: () => void 0, + error: () => void 0, + info: () => void 0, + prefixed: () => blankLogger, +}; diff --git a/packages/frontend-builder/package.json b/packages/frontend-builder/package.json new file mode 100644 index 0000000000..8e77c0f5c3 --- /dev/null +++ b/packages/frontend-builder/package.json @@ -0,0 +1,25 @@ +{ + "name": "frontend-builder", + "type": "module", + "scripts": { + "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", + "typecheck": "tsgo --noEmit", + "lint": "pnpm typecheck && pnpm eslint" + }, + "exports": { + "./*": "./js/*" + }, + "devDependencies": { + "@types/estree": "1.0.8", + "@types/node": "24.10.4", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "rollup": "4.54.0" + }, + "dependencies": { + "i18n": "workspace:*", + "estree-walker": "3.0.3", + "magic-string": "0.30.21", + "vite": "7.3.0" + } +} diff --git a/packages/frontend-builder/rollup-plugin-remove-unref-i18n.ts b/packages/frontend-builder/rollup-plugin-remove-unref-i18n.ts new file mode 100644 index 0000000000..4a2bfa67d9 --- /dev/null +++ b/packages/frontend-builder/rollup-plugin-remove-unref-i18n.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as estreeWalker from 'estree-walker'; +import MagicString from 'magic-string'; +import { assertType } from './utils.js'; +import type { Plugin } from 'vite'; +import type { CallExpression, Expression, Program } from 'estree'; +import type { AstNode } from 'rollup'; + +// This plugin transforms `unref(i18n)` to `i18n` in the code, which is useful for removing unnecessary unref calls +// and helps locale inliner runs after vite build to inline the locale data into the final build. +// +// locale inliner cannot know minifiedSymbol(i18n) is 'unref(i18n)' or 'otherFunctionsWithEffect(i18n)' so +// it is necessary to remove unref calls before minification. +export function pluginRemoveUnrefI18n( + { + i18nSymbolName = 'i18n', + }: { + i18nSymbolName?: string + } = {}): Plugin { + return { + name: 'UnwindCssModuleClassName', + renderChunk(code) { + if (!code.includes('unref(i18n)')) return null; + const ast = this.parse(code) as Program; + const magicString = new MagicString(code); + estreeWalker.walk(ast, { + enter(node) { + if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'unref' + && node.arguments.length === 1) { + // calls to unref with single argument + const arg = node.arguments[0]; + if (arg.type === 'Identifier' && arg.name === i18nSymbolName) { + // this is unref(i18n) so replace it with i18n + // to replace, remove the 'unref(' and the trailing ')' + assertType(node); + assertType(arg); + magicString.remove(node.start, arg.start); + magicString.remove(arg.end, node.end); + } + } + }, + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ hires: true }), + }; + }, + }; +} diff --git a/packages/frontend-builder/tsconfig.json b/packages/frontend-builder/tsconfig.json new file mode 100644 index 0000000000..ab943fded4 --- /dev/null +++ b/packages/frontend-builder/tsconfig.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "declarationMap": true, + "sourceMap": false, + "noEmit": true, + "removeComments": true, + "resolveJsonModule": true, + "strict": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "typeRoots": [ + "./@types", + "./node_modules/@types" + ], + "lib": [ + "esnext" + ] + } +} diff --git a/packages/frontend-builder/utils.ts b/packages/frontend-builder/utils.ts new file mode 100644 index 0000000000..71ffebe03e --- /dev/null +++ b/packages/frontend-builder/utils.ts @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function assertNever(x: never): never { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + throw new Error(`Unexpected type: ${(x as any)?.type ?? x}`); +} + +export function assertType(node: unknown): asserts node is T { +} diff --git a/packages/frontend-embed/@types/global.d.ts b/packages/frontend-embed/@types/global.d.ts index 1025d1bedb..8a067a78ec 100644 --- a/packages/frontend-embed/@types/global.d.ts +++ b/packages/frontend-embed/@types/global.d.ts @@ -10,9 +10,6 @@ declare const _VERSION_: string; declare const _ENV_: string; declare const _DEV_: boolean; declare const _PERF_PREFIX_: string; -declare const _DATA_TRANSFER_DRIVE_FILE_: string; -declare const _DATA_TRANSFER_DRIVE_FOLDER_: string; -declare const _DATA_TRANSFER_DECK_COLUMN_: string; // for dev-mode declare const _LANGS_FULL_: string[][]; diff --git a/packages/frontend-embed/build.ts b/packages/frontend-embed/build.ts new file mode 100644 index 0000000000..4e1f588802 --- /dev/null +++ b/packages/frontend-embed/build.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs/promises'; +import url from 'node:url'; +import path from 'node:path'; +import { execa } from 'execa'; +import locales from 'i18n'; +import { LocaleInliner } from '../frontend-builder/locale-inliner.js' +import { createLogger } from '../frontend-builder/logger'; + +// requires node 21 or later +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); +const outputDir = __dirname + '/../../built/_frontend_embed_vite_'; + +/** + * @return {Promise} + */ +async function viteBuild() { + await execa('vite', ['build'], { + cwd: __dirname, + stdout: process.stdout, + stderr: process.stderr, + }); +} + + +async function buildAllLocale() { + const logger = createLogger() + const inliner = await LocaleInliner.create({ + outputDir, + logger, + scriptsDir: 'scripts', + i18nFile: 'src/i18n.ts', + }) + + await inliner.loadFiles(); + + inliner.collectsModifications(); + + await inliner.saveAllLocales(locales); + + if (logger.errorCount > 0) { + throw new Error(`Build failed with ${logger.errorCount} errors and ${logger.warningCount} warnings.`); + } +} + +async function build() { + await fs.rm(outputDir, { recursive: true, force: true }); + await viteBuild(); + await buildAllLocale(); +} + +await build(); diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js index 7805256fd4..d1ca70617b 100644 --- a/packages/frontend-embed/eslint.config.js +++ b/packages/frontend-embed/eslint.config.js @@ -30,9 +30,6 @@ export default [ _VERSION_: false, _ENV_: false, _PERF_PREFIX_: false, - _DATA_TRANSFER_DRIVE_FILE_: false, - _DATA_TRANSFER_DRIVE_FOLDER_: false, - _DATA_TRANSFER_DECK_COLUMN_: false, }, parser, parserOptions: { @@ -48,9 +45,71 @@ export default [ allowSingleExtends: true, }], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため - // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため - 'id-denylist': ['error', 'window', 'e'], + // window ... グローバルスコープと衝突し、予期せぬ結果を招くため + // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため + // close ... window.closeと衝突 or 紛らわしい + // open ... window.openと衝突 or 紛らわしい + // fetch ... window.fetchと衝突 or 紛らわしい + // location ... window.locationと衝突 or 紛らわしい + // document ... window.documentと衝突 or 紛らわしい + // history ... window.historyと衝突 or 紛らわしい + // scroll ... window.scrollと衝突 or 紛らわしい + // setTimeout ... window.setTimeoutと衝突 or 紛らわしい + // setInterval ... window.setIntervalと衝突 or 紛らわしい + // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい + // clearInterval ... window.clearIntervalと衝突 or 紛らわしい + 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'], + 'no-restricted-globals': [ + 'error', + { + 'name': 'open', + 'message': 'Use `window.open`.', + }, + { + 'name': 'close', + 'message': 'Use `window.close`.', + }, + { + 'name': 'fetch', + 'message': 'Use `window.fetch`.', + }, + { + 'name': 'location', + 'message': 'Use `window.location`.', + }, + { + 'name': 'document', + 'message': 'Use `window.document`.', + }, + { + 'name': 'history', + 'message': 'Use `window.history`.', + }, + { + 'name': 'scroll', + 'message': 'Use `window.scroll`.', + }, + { + 'name': 'setTimeout', + 'message': 'Use `window.setTimeout`.', + }, + { + 'name': 'setInterval', + 'message': 'Use `window.setInterval`.', + }, + { + 'name': 'clearTimeout', + 'message': 'Use `window.clearTimeout`.', + }, + { + 'name': 'clearInterval', + 'message': 'Use `window.clearInterval`.', + }, + { + 'name': 'name', + 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている', + }, + ], 'no-shadow': ['warn'], 'vue/attributes-order': ['error', { alphabetical: false, @@ -85,7 +144,15 @@ export default [ 'vue/return-in-computed-property': 'warn', 'vue/no-setup-props-reactivity-loss': 'warn', 'vue/max-attributes-per-line': 'off', - 'vue/html-self-closing': 'off', + 'vue/html-self-closing': ['error', { + html: { + void: 'any', + normal: 'never', + component: 'any', + }, + svg: 'any', + math: 'any', + }], 'vue/singleline-html-element-content-newline': 'off', 'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true, diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index c253a653ee..6af553e745 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -4,67 +4,64 @@ "type": "module", "scripts": { "watch": "vite", - "build": "vite build", + "build": "tsx build.ts", "typecheck": "vue-tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { - "@discordapp/twemoji": "15.1.0", + "@discordapp/twemoji": "16.0.1", "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.1.4", - "@tabler/icons-webfont": "3.31.0", - "@twemoji/parser": "15.1.1", - "@vitejs/plugin-vue": "5.2.3", - "@vue/compiler-sfc": "3.5.13", - "astring": "1.9.0", + "@rollup/plugin-replace": "6.0.3", + "@rollup/pluginutils": "5.3.0", + "@twemoji/parser": "16.0.0", + "@vitejs/plugin-vue": "6.0.3", "buraha": "0.0.1", "estree-walker": "3.0.3", "frontend-shared": "workspace:*", + "i18n": "workspace:*", + "icons-subsetter": "workspace:*", "json5": "2.2.3", - "mfm-js": "0.24.0", + "mfm-js": "0.25.0", "misskey-js": "workspace:*", "punycode.js": "2.3.1", - "rollup": "4.39.0", - "sass": "1.86.3", - "shiki": "3.2.2", + "rollup": "4.54.0", + "sass": "1.97.1", + "shiki": "3.20.0", "tinycolor2": "1.6.0", - "tsc-alias": "1.8.15", - "tsconfig-paths": "4.2.0", - "typescript": "5.8.3", - "uuid": "11.1.0", - "vite": "6.3.1", - "vue": "3.5.13" + "uuid": "13.0.0", + "vite": "7.3.0", + "vue": "3.5.26" }, "devDependencies": { - "@misskey-dev/summaly": "5.2.0", + "@misskey-dev/summaly": "5.2.5", + "@tabler/icons-webfont": "3.35.0", "@testing-library/vue": "8.1.0", - "@types/estree": "1.0.7", - "@types/micromatch": "4.0.9", - "@types/node": "22.14.0", + "@types/estree": "1.0.8", + "@types/micromatch": "4.0.10", + "@types/node": "24.10.4", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@vitest/coverage-v8": "3.1.1", - "@vue/runtime-core": "3.5.13", - "acorn": "8.14.1", - "cross-env": "7.0.3", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-vue": "10.0.0", - "fast-glob": "3.3.3", - "happy-dom": "17.4.4", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@vitest/coverage-v8": "4.0.16", + "@vue/runtime-core": "3.5.26", + "acorn": "8.15.0", + "cross-env": "10.1.0", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-vue": "10.6.2", + "happy-dom": "20.0.11", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.7.3", - "nodemon": "3.1.9", - "prettier": "3.5.3", - "start-server-and-test": "2.0.11", + "msw": "2.12.6", + "nodemon": "3.1.11", + "prettier": "3.7.4", + "start-server-and-test": "2.1.3", + "tsx": "4.21.0", "vite-plugin-turbosnap": "1.0.3", - "vue-component-type-helpers": "2.2.8", - "vue-eslint-parser": "10.1.3", - "vue-tsc": "2.2.8" + "vue-component-type-helpers": "3.2.1", + "vue-eslint-parser": "10.2.0", + "vue-tsc": "3.2.1" } } diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/frontend-embed/public/loader/boot.js similarity index 72% rename from packages/backend/src/server/web/boot.embed.js rename to packages/frontend-embed/public/loader/boot.js index 9de1275380..9b3d27873b 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/frontend-embed/public/loader/boot.js @@ -32,61 +32,30 @@ } //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } - - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } - - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); + const supportedLangs = LANGS; + /** @type { string } */ + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; } else { - renderError('LOCALE_FETCH'); - return; + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + if (lang == null) lang = 'en-US'; } } + + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } //#endregion //#region Script async function importAppScript() { - await import(`/embed_vite/${CLIENT_ENTRY}`) + await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/boot.ts') .catch(async e => { console.error(e); renderError('APP_IMPORT'); @@ -101,6 +70,8 @@ importAppScript(); }); } + + localStorage.setItem('lang', lang); //#endregion async function addStyle(styleText) { @@ -115,10 +86,26 @@ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - const locale = JSON.parse(localStorage.getItem('locale') || '{}'); + let messages = null; + const bootloaderLocales = localStorage.getItem('bootloaderLocales'); + if (bootloaderLocales) { + messages = JSON.parse(bootloaderLocales); + } + if (!messages) { + // older version of misskey does not store bootloaderLocales, stores locale as a whole + const legacyLocale = localStorage.getItem('locale'); + if (legacyLocale) { + const parsed = JSON.parse(legacyLocale); + messages = { + ...(parsed._bootErrors ?? {}), + reload: parsed.reload, + }; + } + } + if (!messages) messages = {}; - const title = locale?._bootErrors?.title || 'Failed to initialize Misskey'; - const reload = locale?.reload || 'Reload'; + const title = messages?.title || 'Failed to initialize Misskey'; + const reload = messages?.reload || 'Reload'; document.body.innerHTML = `
${title}
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/frontend-embed/public/loader/style.css similarity index 98% rename from packages/backend/src/server/web/style.embed.css rename to packages/frontend-embed/public/loader/style.css index 5e8786cc4e..0911d562bf 100644 --- a/packages/backend/src/server/web/style.embed.css +++ b/packages/frontend-embed/public/loader/style.css @@ -53,6 +53,7 @@ html.embed.noborder #splash { margin: auto; width: 64px; height: 64px; + border-radius: 10px; pointer-events: none; } diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index c1b2b58beb..961cbcef66 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -6,29 +6,34 @@ // https://vitejs.dev/config/build-options.html#build-modulepreload import 'vite/modulepreload-polyfill'; -import '@tabler/icons-webfont/dist/tabler-icons.scss'; +if (import.meta.env.DEV) { + await import('@tabler/icons-webfont/dist/tabler-icons.scss'); +} else { + await import('icons-subsetter/built/tabler-icons-frontendEmbed.css'); +} import '@/style.scss'; import { createApp, defineAsyncComponent } from 'vue'; import defaultLightTheme from '@@/themes/l-light.json5'; import defaultDarkTheme from '@@/themes/d-dark.json5'; import { MediaProxy } from '@@/js/media-proxy.js'; +import { storeBootloaderErrors } from '@@/js/store-boot-errors'; import { applyTheme, assertIsTheme } from '@/theme.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; import { DI } from '@/di.js'; import { serverMetadata } from '@/server-metadata.js'; -import { url, version, locale, lang, updateLocale } from '@@/js/config.js'; +import { url, version, lang } from '@@/js/config.js'; import { parseEmbedParams } from '@@/js/embed-page.js'; import { postMessageToParentWindow, setIframeId } from '@/post-message.js'; import { serverContext } from '@/server-context.js'; -import { i18n, updateI18n } from '@/i18n.js'; +import { i18n } from '@/i18n.js'; import type { Theme } from '@/theme.js'; console.log('Misskey Embed'); //#region Embedパラメータの取得・パース -const params = new URLSearchParams(location.search); +const params = new URLSearchParams(window.location.search); const embedParams = parseEmbedParams(params); if (_DEV_) console.log(embedParams); //#endregion @@ -72,23 +77,11 @@ if (embedParams.colorMode === 'dark') { //#endregion //#region Detect language & fetch translations -const localeVersion = localStorage.getItem('localeVersion'); -const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null); -if (localeOutdated) { - const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); - if (res.status === 200) { - const newLocale = await res.text(); - const parsedNewLocale = JSON.parse(newLocale); - localStorage.setItem('locale', newLocale); - localStorage.setItem('localeVersion', version); - updateLocale(parsedNewLocale); - updateI18n(parsedNewLocale); - } -} +storeBootloaderErrors({ ...i18n.ts._bootErrors, reload: i18n.ts.reload }); //#endregion // サイズの制限 -document.documentElement.style.maxWidth = '500px'; +window.document.documentElement.style.maxWidth = '500px'; // iframeIdの設定 function setIframeIdHandler(event: MessageEvent) { @@ -121,16 +114,16 @@ app.provide(DI.embedParams, embedParams); const rootEl = ((): HTMLElement => { const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); + const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID); if (currentRoot) { console.warn('multiple import detected'); return currentRoot; } - const root = document.createElement('div'); + const root = window.document.createElement('div'); root.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(root); + window.document.body.appendChild(root); return root; })(); @@ -166,7 +159,7 @@ console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hu //#endregion function removeSplash() { - const splash = document.getElementById('splash'); + const splash = window.document.getElementById('splash'); if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; diff --git a/packages/frontend-embed/src/components/EmAvatar.vue b/packages/frontend-embed/src/components/EmAvatar.vue index 58c35c8ef0..3f91e14403 100644 --- a/packages/frontend-embed/src/components/EmAvatar.vue +++ b/packages/frontend-embed/src/components/EmAvatar.vue @@ -9,16 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue index 0bff048ce4..be18ce79d5 100644 --- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue +++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -19,7 +19,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha const canvasPromise = new Promise(resolve => { // テスト環境で Web Worker インスタンスは作成できない if (import.meta.env.MODE === 'test') { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); @@ -34,7 +34,7 @@ const canvasPromise = new Promise(resol ); resolve(workers); } else { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue index 4a116e317a..7add3bb53f 100644 --- a/packages/frontend-embed/src/components/EmInstanceTicker.vue +++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue @@ -29,7 +29,7 @@ const props = defineProps<{ // if no instance data is given, this is for the local instance const instance = props.instance ?? { name: serverMetadata.name, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content, + themeColor: (window.document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content, }; const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico'); diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index 2c96ce3215..8cb90247fc 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only target="_blank" rel="noopener" > - import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; -import ImgWithBlurhash from '@/components/EmImgWithBlurhash.vue'; +import EmImgWithBlurhash from '@/components/EmImgWithBlurhash.vue'; import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ @@ -64,7 +64,7 @@ const url = computed(() => (props.raw) : props.image.thumbnailUrl, ); -async function onclick(ev: MouseEvent) { +async function onclick(ev: PointerEvent) { if (hide.value) { ev.stopPropagation(); hide.value = false; diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue index b5aaa95894..0a8ac9c05a 100644 --- a/packages/frontend-embed/src/components/EmMention.vue +++ b/packages/frontend-embed/src/components/EmMention.vue @@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us const url = `/${canonical}`; -const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention')); +const bg = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-mention')); bg.setAlpha(0.1); const bgCss = bg.toRgbString(); diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index 1f9ce9d4f4..5b9a53bbc2 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -299,7 +299,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext items.value.size === 0); const error = ref(false); -const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body); +const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body); const visibility = useDocumentVisibility(); @@ -353,7 +353,7 @@ watch(visibility, () => { BACKGROUND_PAUSE_WAIT_SEC * 1000); } else { // 'visible' if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } else { isPausingUpdate = false; @@ -447,11 +447,11 @@ onBeforeMount(() => { init().then(() => { if (props.pagination.reversed) { nextTick(() => { - setTimeout(toBottom, 800); + window.setTimeout(toBottom, 800); // scrollToBottomでmoreFetchingボタンが画面外まで出るまで // more = trueを遅らせる - setTimeout(() => { + window.setTimeout(() => { moreFetching.value = false; }, 2000); }); @@ -461,11 +461,11 @@ onBeforeMount(() => { onBeforeUnmount(() => { if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } if (preventAppearFetchMoreTimer.value) { - clearTimeout(preventAppearFetchMoreTimer.value); + window.clearTimeout(preventAppearFetchMoreTimer.value); preventAppearFetchMoreTimer.value = null; } scrollObserver.value?.disconnect(); diff --git a/packages/frontend-embed/src/components/I18n.vue b/packages/frontend-embed/src/components/I18n.vue index b621110ec9..9866e50958 100644 --- a/packages/frontend-embed/src/components/I18n.vue +++ b/packages/frontend-embed/src/components/I18n.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue index 4b00ae7c2d..8c6cd32bab 100644 --- a/packages/frontend-embed/src/pages/tag.vue +++ b/packages/frontend-embed/src/pages/tag.vue @@ -40,15 +40,15 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkAnimBg.fragment.glsl b/packages/frontend/src/components/MkAnimBg.fragment.glsl new file mode 100644 index 0000000000..d40872bb7a --- /dev/null +++ b/packages/frontend/src/components/MkAnimBg.fragment.glsl @@ -0,0 +1,111 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float snoise(vec2 v) { + const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = mod289(i); + vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); + + vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); + m = m*m; + m = m*m; + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +in vec2 in_uv; +uniform float u_time; +uniform vec2 u_resolution; +uniform float u_spread; +uniform float u_speed; +uniform float u_warp; +uniform float u_focus; +uniform float u_itensity; +out vec4 out_color; + +float circle(in vec2 _pos, in vec2 _origin, in float _radius) { + float SPREAD = 0.7 * u_spread; + float SPEED = 0.00055 * u_speed; + float WARP = 1.5 * u_warp; + float FOCUS = 1.15 * u_focus; + + vec2 dist = _pos - _origin; + + float distortion = snoise(vec2( + _pos.x * 1.587 * WARP + u_time * SPEED * 0.5, + _pos.y * 1.192 * WARP + u_time * SPEED * 0.3 + )) * 0.5 + 0.5; + + float feather = 0.01 + SPREAD * pow(distortion, FOCUS); + + return 1.0 - smoothstep( + _radius - (_radius * feather), + _radius + (_radius * feather), + dot( dist, dist ) * 4.0 + ); +} + +void main() { + vec3 green = vec3(1.0) - vec3(153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0); + vec3 purple = vec3(1.0) - vec3(195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0); + vec3 orange = vec3(1.0) - vec3(255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0); + + float ratio = u_resolution.x / u_resolution.y; + + vec2 uv = vec2(in_uv.x, in_uv.y / ratio) * 0.5 + 0.5; + + vec3 color = vec3(0.0); + + float greenMix = snoise(in_uv * 1.31 + u_time * 0.8 * 0.00017) * 0.5 + 0.5; + float purpleMix = snoise(in_uv * 1.26 + u_time * 0.8 * -0.0001) * 0.5 + 0.5; + float orangeMix = snoise(in_uv * 1.34 + u_time * 0.8 * 0.00015) * 0.5 + 0.5; + + float alphaOne = 0.35 + 0.65 * pow(snoise(vec2(u_time * 0.00012, uv.x)) * 0.5 + 0.5, 1.2); + float alphaTwo = 0.35 + 0.65 * pow(snoise(vec2((u_time + 1561.0) * 0.00014, uv.x )) * 0.5 + 0.5, 1.2); + float alphaThree = 0.35 + 0.65 * pow(snoise(vec2((u_time + 3917.0) * 0.00013, uv.x )) * 0.5 + 0.5, 1.2); + + color += vec3(circle(uv, vec2(0.22 + sin(u_time * 0.000201) * 0.06, 0.80 + cos(u_time * 0.000151) * 0.06), 0.15)) * alphaOne * (purple * purpleMix + orange * orangeMix); + color += vec3(circle(uv, vec2(0.90 + cos(u_time * 0.000166) * 0.06, 0.42 + sin(u_time * 0.000138) * 0.06), 0.18)) * alphaTwo * (green * greenMix + purple * purpleMix); + color += vec3(circle(uv, vec2(0.19 + sin(u_time * 0.000112) * 0.06, 0.25 + sin(u_time * 0.000192) * 0.06), 0.09)) * alphaThree * (orange * orangeMix); + + color *= u_itensity + 1.0 * pow(snoise(vec2(in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009)) * 0.5 + 0.5, 2.0); + + vec3 inverted = vec3(1.0) - color; + out_color = vec4(color, max(max(color.x, color.y), color.z)); +} diff --git a/packages/frontend/src/components/MkAnimBg.vertex.glsl b/packages/frontend/src/components/MkAnimBg.vertex.glsl new file mode 100644 index 0000000000..56d6b017b1 --- /dev/null +++ b/packages/frontend/src/components/MkAnimBg.vertex.glsl @@ -0,0 +1,15 @@ +#version 300 es + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 position; +uniform vec2 u_scale; +out vec2 in_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + in_uv = position / u_scale; +} diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue index e57fbcdee3..bcdc604bb8 100644 --- a/packages/frontend/src/components/MkAnimBg.vue +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts index 627cb0c4ff..743bdda032 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue index 6e5b29654b..da0f618e95 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.vue +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -80,9 +119,12 @@ onMounted(() => { .root { margin: auto; position: relative; - padding: 32px; + padding: 32px 32px 0; min-width: 320px; max-width: 480px; + max-height: 100%; + overflow-y: auto; + overflow-x: hidden; box-sizing: border-box; background: var(--MI_THEME-panel); border-radius: var(--MI-radius); @@ -103,4 +145,14 @@ onMounted(() => { .text { margin: 1em 0; } + +.footer { + position: sticky; + bottom: 0; + left: -32px; + backdrop-filter: var(--MI-blur, blur(15px)); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); + margin: 0 -32px; + padding: 24px 32px; +} diff --git a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts index 4d921a4c48..5c4b05481a 100644 --- a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts +++ b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue index 59099d54bd..a41fdbc45d 100644 --- a/packages/frontend/src/components/MkAntennaEditor.vue +++ b/packages/frontend/src/components/MkAntennaEditor.vue @@ -4,23 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts index 0a569b3beb..4420cc4f05 100644 --- a/packages/frontend/src/components/MkButton.stories.impl.ts +++ b/packages/frontend/src/components/MkButton.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import MkButton from './MkButton.vue'; export const Default = { diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 891af7f696..854ed31ed5 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+ + {{ i18n.ts.youAreAdmin }} +
@@ -48,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts index 78cb4120de..a97cb4fd2f 100644 --- a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts @@ -4,7 +4,7 @@ */ import { HttpResponse, http } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { file } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkCropperDialog from './MkCropperDialog.vue'; @@ -38,7 +38,7 @@ export const Default = { }; }, args: { - file: file(), + imageFile: new File([], 'image.webp', { type: 'image/webp' }), aspectRatio: NaN, }, parameters: { diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index ba21394cbc..1fad936d16 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -15,132 +15,121 @@ SPDX-License-Identifier: AGPL-3.0-only @closed="emit('closed')" > - +
- - diff --git a/packages/frontend/src/components/MkDialog.stories.impl.ts b/packages/frontend/src/components/MkDialog.stories.impl.ts index 57c7916049..c168d31cce 100644 --- a/packages/frontend/src/components/MkDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkDialog.stories.impl.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { expect, userEvent, waitFor, within } from '@storybook/test'; import type { StoryObj } from '@storybook/vue3'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 81d508c161..fb8b38de6d 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -11,18 +11,13 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - + + + + +
@@ -30,20 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only - - - +
{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }} {{ cancelText ?? i18n.ts.cancel }} @@ -55,12 +41,20 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + diff --git a/packages/frontend/src/components/MkDrive.file.stories.impl.ts b/packages/frontend/src/components/MkDrive.file.stories.impl.ts index 933383775c..9981ee77ac 100644 --- a/packages/frontend/src/components/MkDrive.file.stories.impl.ts +++ b/packages/frontend/src/components/MkDrive.file.stories.impl.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import MkDrive_file from './MkDrive.file.vue'; import { file } from '../../.storybook/fakes.js'; diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 70ab60cfae..e2858084c0 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -8,7 +8,6 @@ SPDX-License-Identifier: AGPL-3.0-only :class="[$style.root, { [$style.isSelected]: isSelected }]" draggable="true" :title="title" - @click="onClick" @contextmenu.stop="onContextmenu" @dragstart="onDragstart" @dragend="onDragend" @@ -46,24 +45,18 @@ import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/i.js'; import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js'; -import { deviceKind } from '@/utility/device-kind.js'; -import { useRouter } from '@/router.js'; - -const router = useRouter(); +import { setDragData } from '@/drag-and-drop.js'; const props = withDefaults(defineProps<{ file: Misskey.entities.DriveFile; folder: Misskey.entities.DriveFolder | null; isSelected?: boolean; - selectMode?: boolean; }>(), { isSelected: false, - selectMode: false, }); const emit = defineEmits<{ - (ev: 'chosen', r: Misskey.entities.DriveFile): void; - (ev: 'dragstart'): void; + (ev: 'dragstart', dragEvent: DragEvent): void; (ev: 'dragend'): void; }>(); @@ -71,30 +64,18 @@ const isDragging = ref(false); const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`); -function onClick(ev: MouseEvent) { - if (props.selectMode) { - emit('chosen', props.file); - } else { - if (deviceKind === 'desktop') { - router.push(`/my/drive/file/${props.file.id}`); - } else { - os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); - } - } -} - -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { os.contextMenu(getDriveFileMenu(props.file, props.folder), ev); } function onDragstart(ev: DragEvent) { if (ev.dataTransfer) { ev.dataTransfer.effectAllowed = 'move'; - ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); + setDragData(ev, 'driveFiles', [props.file]); } isDragging.value = true; - emit('dragstart'); + emit('dragstart', ev); } function onDragend() { @@ -114,7 +95,7 @@ function onDragend() { &:hover { background: rgba(#000, 0.05); - > .label { + .label { &::before, &::after { background: #0b65a5; @@ -132,7 +113,7 @@ function onDragend() { &:active { background: rgba(#000, 0.1); - > .label { + .label { &::before, &::after { background: #0b588c; @@ -158,19 +139,19 @@ function onDragend() { background: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); } - > .label { + .label { &::before, &::after { display: none; } } - > .name { - color: #fff; + .name { + color: var(--MI_THEME-fgOnAccent); } - > .thumbnail { - color: #fff; + .thumbnail { + color: var(--MI_THEME-fgOnAccent); } } } @@ -240,8 +221,9 @@ function onDragend() { .name { display: block; - margin: 4px 0 0 0; - font-size: 0.8em; + margin: 8px 0 0 0; + padding: 0 2px; + font-size: 82%; text-align: center; word-break: break-all; color: var(--MI_THEME-fg); diff --git a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts index e6c7c2f645..6fa8d2253f 100644 --- a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts +++ b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { http, HttpResponse } from 'msw'; import * as Misskey from 'misskey-js'; diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 9c72691d21..6d93dfc0d4 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -8,7 +8,6 @@ SPDX-License-Identifier: AGPL-3.0-only :class="[$style.root, { [$style.draghover]: draghover }]" draggable="true" :title="title" - @click="onClick" @contextmenu.stop="onContextmenu" @mouseover="onMouseover" @mouseout="onMouseout" @@ -19,16 +18,15 @@ SPDX-License-Identifier: AGPL-3.0-only @dragstart="onDragstart" @dragend="onDragend" > -

- - - {{ folder.name }} -

-

+ + + +

{{ folder.name }}
+
{{ i18n.ts.uploadFolder }} -

+
@@ -43,6 +41,9 @@ import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/utility/achievements.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { prefer } from '@/preferences.js'; +import { globalEvents } from '@/events.js'; +import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js'; +import { selectDriveFolder } from '@/utility/drive.js'; const props = withDefaults(defineProps<{ folder: Misskey.entities.DriveFolder; @@ -56,10 +57,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'chosen', v: Misskey.entities.DriveFolder): void; (ev: 'unchose', v: Misskey.entities.DriveFolder): void; - (ev: 'move', v: Misskey.entities.DriveFolder): void; - (ev: 'upload', file: File, folder: Misskey.entities.DriveFolder); - (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; - (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'upload', files: File[], folder: Misskey.entities.DriveFolder): void; (ev: 'dragstart'): void; (ev: 'dragend'): void; }>(); @@ -78,10 +76,6 @@ function checkboxClicked() { } } -function onClick() { - emit('move', props.folder); -} - function onMouseover() { hover.value = true; } @@ -101,10 +95,7 @@ function onDragover(ev: DragEvent) { } const isFile = ev.dataTransfer.items[0].kind === 'file'; - const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; - - if (isFile || isDriveFile || isDriveFolder) { + if (isFile || checkDragDataType(ev, ['driveFiles', 'driveFolders'])) { switch (ev.dataTransfer.effectAllowed) { case 'all': case 'uninitialized': @@ -141,55 +132,64 @@ function onDrop(ev: DragEvent) { // ファイルだったら if (ev.dataTransfer.files.length > 0) { - for (const file of Array.from(ev.dataTransfer.files)) { - emit('upload', file, props.folder); - } + emit('upload', Array.from(ev.dataTransfer.files), props.folder); return; } //#region ドライブのファイル - const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile !== '') { - const file = JSON.parse(driveFile); - emit('removeFile', file.id); - misskeyApi('drive/files/update', { - fileId: file.id, - folderId: props.folder.id, - }); + { + const droppedData = getDragData(ev, 'driveFiles'); + if (droppedData != null) { + misskeyApi('drive/files/move-bulk', { + fileIds: droppedData.map(f => f.id), + folderId: props.folder.id, + }).then(() => { + globalEvents.emit('driveFilesUpdated', droppedData.map(x => ({ + ...x, + folderId: props.folder.id, + folder: props.folder, + }))); + }); + } } //#endregion //#region ドライブのフォルダ - const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder !== '') { - const folder = JSON.parse(driveFolder); + { + const droppedData = getDragData(ev, 'driveFolders'); + if (droppedData != null) { + const droppedFolder = droppedData[0]; - // 移動先が自分自身ならreject - if (folder.id === props.folder.id) return; + // 移動先が自分自身ならreject + if (droppedFolder.id === props.folder.id) return; - emit('removeFolder', folder.id); - misskeyApi('drive/folders/update', { - folderId: folder.id, - parentId: props.folder.id, - }).then(() => { - // noop - }).catch(err => { - switch (err.code) { - case 'RECURSIVE_NESTING': - claimAchievement('driveFolderCircularReference'); - os.alert({ - type: 'error', - title: i18n.ts.unableToProcess, - text: i18n.ts.circularReferenceFolder, - }); - break; - default: - os.alert({ - type: 'error', - text: i18n.ts.somethingHappened, - }); - } - }); + misskeyApi('drive/folders/update', { + folderId: droppedFolder.id, + parentId: props.folder.id, + }).then(() => { + globalEvents.emit('driveFoldersUpdated', [droppedFolder].map(x => ({ + ...x, + parentId: props.folder.id, + parent: props.folder, + }))); + }).catch(err => { + switch (err.code) { + case 'RECURSIVE_NESTING': + claimAchievement('driveFolderCircularReference'); + os.alert({ + type: 'error', + title: i18n.ts.unableToProcess, + text: i18n.ts.circularReferenceFolder, + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); + } + }); + } } //#endregion } @@ -198,7 +198,7 @@ function onDragstart(ev: DragEvent) { if (!ev.dataTransfer) return; ev.dataTransfer.effectAllowed = 'move'; - ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); + setDragData(ev, 'driveFolders', [props.folder]); isDragging.value = true; // 親ブラウザに対して、ドラッグが開始されたフラグを立てる @@ -211,10 +211,6 @@ function onDragend() { emit('dragend'); } -function go() { - emit('move', props.folder); -} - function rename() { os.inputText({ title: i18n.ts.renameFolder, @@ -225,17 +221,28 @@ function rename() { misskeyApi('drive/folders/update', { folderId: props.folder.id, name: name, + }).then(() => { + globalEvents.emit('driveFoldersUpdated', [{ + ...props.folder, + name: name, + }]); }); }); } function move() { - os.selectDriveFolder(false).then(folder => { - if (folder[0] && folder[0].id === props.folder.id) return; + selectDriveFolder(null).then(({ canceled, folders }) => { + if (canceled || (folders[0] && folders[0].id === props.folder.id)) return; misskeyApi('drive/folders/update', { folderId: props.folder.id, - parentId: folder[0] ? folder[0].id : null, + parentId: folders[0] ? folders[0].id : null, + }).then(() => { + globalEvents.emit('driveFoldersUpdated', [{ + ...props.folder, + parentId: folders[0] ? folders[0].id : null, + parent: folders[0] ?? null, + }]); }); }); } @@ -247,6 +254,7 @@ function deleteFolder() { if (prefer.s.uploadFolder === props.folder.id) { prefer.commit('uploadFolder', null); } + globalEvents.emit('driveFoldersDeleted', [props.folder]); }).catch(err => { switch (err.id) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': @@ -269,13 +277,13 @@ function setAsUploadFolder() { prefer.commit('uploadFolder', props.folder.id); } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { let menu: MenuItem[]; menu = [{ text: i18n.ts.openInWindow, icon: 'ti ti-app-window', - action: () => { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkDriveWindow.vue')), { + action: async () => { + const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkDriveWindow.vue').then(x => x.default), { initialFolder: props.folder, }, { closed: () => dispose(), @@ -311,10 +319,9 @@ function onContextmenu(ev: MouseEvent) { diff --git a/packages/frontend/src/components/MkDriveFolderSelectDialog.vue b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue new file mode 100644 index 0000000000..d5b6b0cbec --- /dev/null +++ b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue index c0142ec76e..0b8d0bfb8a 100644 --- a/packages/frontend/src/components/MkDriveWindow.vue +++ b/packages/frontend/src/components/MkDriveWindow.vue @@ -14,19 +14,19 @@ SPDX-License-Identifier: AGPL-3.0-only - + diff --git a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts index bf4158a2c8..cc934040f5 100644 --- a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts +++ b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { expect, userEvent, waitFor, within } from '@storybook/test'; import type { StoryObj } from '@storybook/vue3'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index d4367f6ee8..bf0f9d0130 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -64,6 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only +
@@ -139,6 +140,10 @@ import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-e import { $i } from '@/i.js'; import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; import { prefer } from '@/preferences.js'; +import { useRouter } from '@/router.js'; +import { haptic } from '@/utility/haptic.js'; + +const router = useRouter(); const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -147,7 +152,7 @@ const props = withDefaults(defineProps<{ asDrawer?: boolean; asWindow?: boolean; asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう - targetNote?: Misskey.entities.Note; + targetNote?: Misskey.entities.Note | null; }>(), { showPinned: true, }); @@ -321,7 +326,7 @@ watch(q, () => { for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { for (const emoji of emojis) { - if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) { + if (keywords.every(keyword => index[emoji.char]?.some(k => k.includes(keyword)))) { matches.add(emoji); if (matches.size >= max) break; } @@ -338,7 +343,7 @@ watch(q, () => { for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { for (const emoji of emojis) { - if (index[emoji.char].some(k => k.startsWith(newQ))) { + if (index[emoji.char]?.some(k => k.startsWith(newQ))) { matches.add(emoji); if (matches.size >= max) break; } @@ -355,7 +360,7 @@ watch(q, () => { for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { for (const emoji of emojis) { - if (index[emoji.char].some(k => k.includes(newQ))) { + if (index[emoji.char]?.some(k => k.includes(newQ))) { matches.add(emoji); if (matches.size >= max) break; } @@ -407,13 +412,13 @@ function getDef(emoji: string): string | Misskey.entities.EmojiSimple | UnicodeE } /** @see MkEmojiPicker.section.vue */ -function computeButtonTitle(ev: MouseEvent): void { +function computeButtonTitle(ev: PointerEvent): void { const elm = ev.target as HTMLElement; const emoji = elm.dataset.emoji as string; elm.title = getEmojiName(emoji); } -function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) { +function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: PointerEvent) { const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; if (el && prefer.s.animation) { const rect = el.getBoundingClientRect(); @@ -427,6 +432,8 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, const key = getKey(emoji); emit('chosen', key); + haptic(); + // 最近使った絵文字更新 if (!pinned.value?.includes(key)) { let recents = store.s.recentlyUsedEmojis; @@ -489,6 +496,11 @@ function done(query?: string): boolean | void { } } +function settings() { + emit('esc'); + router.push('/settings/emoji-palette'); +} + onMounted(() => { focus(); }); @@ -518,47 +530,53 @@ defineExpose({ --eachSize: 50px; } + &.s4 { + --eachSize: 55px; + } + + &.s5 { + --eachSize: 60px; + } + &.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; @@ -573,9 +591,17 @@ defineExpose({ > .body { display: grid; - grid-template-columns: var(--columns); + grid-template-columns: repeat(var(--columns), 1fr); font-size: 30px; + > .config { + aspect-ratio: 1 / 1; + width: auto; + height: auto; + min-width: 0; + font-size: 14px; + } + > .item { aspect-ratio: 1 / 1; width: auto; @@ -607,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 { @@ -675,13 +701,8 @@ defineExpose({ height: 100%; overflow-y: auto; overflow-x: hidden; - scrollbar-width: none; - &::-webkit-scrollbar { - display: none; - } - > .group { &:not(.index) { padding: 4px 0 8px 0; @@ -720,6 +741,15 @@ defineExpose({ position: relative; padding: $pad; + > .config { + position: relative; + padding: 0 3px; + width: var(--eachSize); + height: var(--eachSize); + contain: strict; + opacity: 0.5; + } + > .item { position: relative; padding: 0 3px; diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 662e2a118d..0ff4e8f38d 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :hasInteractionWithOtherFocusTrappedEls="true" :transparentBg="true" :manualShowing="manualShowing" - :src="src" + :anchorElement="anchorElement" @click="modal?.close()" @esc="modal?.close()" @opening="opening" @@ -44,11 +44,11 @@ import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ manualShowing?: boolean | null; - src?: HTMLElement; + anchorElement?: HTMLElement | null; showPinned?: boolean; pinnedEmojis?: string[], asReactionPicker?: boolean; - targetNote?: Misskey.entities.Note; + targetNote?: Misskey.entities.Note | null; choseAndClose?: boolean; }>(), { manualShowing: null, diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue index a2247d844b..3f7eb9bccd 100644 --- a/packages/frontend/src/components/MkExtensionInstaller.vue +++ b/packages/frontend/src/components/MkExtensionInstaller.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }} -
+
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -78,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
{{ i18n.ts.cancel }} @@ -101,6 +101,8 @@ SPDX-License-Identifier: AGPL-3.0-only - diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index b9888d9b64..0fa7bea7ab 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -31,9 +31,10 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 81689397cc..864f53d09c 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -19,13 +19,42 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
-
+
+ + + + + + + +
+ +
+
+ +
+ + +
+
+
+ +
- +
- +
@@ -67,9 +96,12 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index b65f610986..72a24411c1 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -46,6 +46,7 @@ import { claimAchievement } from '@/utility/achievements.js'; import { pleaseLogin } from '@/utility/please-login.js'; import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; +import { haptic } from '@/utility/haptic.js'; const props = withDefaults(defineProps<{ user: Misskey.entities.UserDetailed, @@ -80,10 +81,18 @@ function onFollowChange(user: Misskey.entities.UserDetailed) { } async function onClick() { - pleaseLogin({ openOnRemote: { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` } }); + const isLoggedIn = await pleaseLogin({ + openOnRemote: { + type: 'web', + path: `/@${props.user.username}@${props.user.host ?? host}`, + }, + }); + if (!isLoggedIn) return; wait.value = true; + haptic(); + try { if (isFollowing.value) { const { canceled } = await os.confirm({ @@ -99,6 +108,21 @@ async function onClick() { await misskeyApi('following/delete', { userId: props.user.id, }); + } else if (hasPendingFollowRequestFromYou.value) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.tsx.cancelFollowRequestConfirm({ name: props.user.name || props.user.username }), + }); + + if (canceled) { + wait.value = false; + return; + } + + await misskeyApi('following/requests/cancel', { + userId: props.user.id, + }); + hasPendingFollowRequestFromYou.value = false; } else { if (prefer.s.alwaysConfirmFollow) { const { canceled } = await os.confirm({ @@ -112,41 +136,34 @@ async function onClick() { } } - if (hasPendingFollowRequestFromYou.value) { - await misskeyApi('following/requests/cancel', { - userId: props.user.id, - }); - hasPendingFollowRequestFromYou.value = false; - } else { - await misskeyApi('following/create', { - userId: props.user.id, - withReplies: prefer.s.defaultFollowWithReplies, - }); - emit('update:user', { - ...props.user, - withReplies: prefer.s.defaultFollowWithReplies, - }); - hasPendingFollowRequestFromYou.value = true; + await misskeyApi('following/create', { + userId: props.user.id, + withReplies: prefer.s.defaultFollowWithReplies, + }); + emit('update:user', { + ...props.user, + withReplies: prefer.s.defaultFollowWithReplies, + }); + hasPendingFollowRequestFromYou.value = true; - if ($i == null) { - wait.value = false; - return; - } + if ($i == null) { + wait.value = false; + return; + } - claimAchievement('following1'); + claimAchievement('following1'); - if ($i.followingCount >= 10) { - claimAchievement('following10'); - } - if ($i.followingCount >= 50) { - claimAchievement('following50'); - } - if ($i.followingCount >= 100) { - claimAchievement('following100'); - } - if ($i.followingCount >= 300) { - claimAchievement('following300'); - } + if ($i.followingCount >= 10) { + claimAchievement('following10'); + } + if ($i.followingCount >= 50) { + claimAchievement('following50'); + } + if ($i.followingCount >= 100) { + claimAchievement('following100'); + } + if ($i.followingCount >= 300) { + claimAchievement('following300'); } } } catch (err) { diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue index 35112ad45d..a2843a3503 100644 --- a/packages/frontend/src/components/MkForgotPassword.vue +++ b/packages/frontend/src/components/MkForgotPassword.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only > - +
@@ -34,12 +34,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._forgotPassword.contactAdmin }}
- +
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 4756079e76..091721b40b 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only :width="450" :canClose="false" :withOkButton="true" - :okButtonDisabled="false" + :okButtonDisabled="!canSave" @click="cancel()" @ok="ok()" @close="cancel()" @@ -19,71 +19,17 @@ SPDX-License-Identifier: AGPL-3.0-only {{ title }} - -
- -
-
- -
{{ i18n.ts.nothing }}
-
-
+
+ +
@@ -73,6 +76,14 @@ withDefaults(defineProps<{ margin-right: calc(calc(var(--fukidashi-radius) * .13) * -1); } } + + &.fullWidth { + width: 100%; + + &.content { + width: 100%; + } + } } .bg { @@ -85,6 +96,7 @@ withDefaults(defineProps<{ .content { position: relative; padding: 10px 14px; + box-sizing: border-box; } @container (max-width: 450px) { diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 49a6c65170..e6fae285b3 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- import * as Misskey from 'misskey-js'; import { computed, ref } from 'vue'; -import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; +import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { prefer } from '@/preferences.js'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index 28bb936755..03780bf3ba 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -18,7 +18,7 @@ import { Chart } from 'chart.js'; import * as Misskey from 'misskey-js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/use/use-chart-tooltip.js'; +import { useChartTooltip } from '@/composables/use-chart-tooltip.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; @@ -125,8 +125,7 @@ async function renderChart() { data: format(values) as any, borderWidth: 0, borderRadius: 3, - backgroundColor(c) { - // @ts-expect-error TS(2339) + backgroundColor(c: any) { const value = c.dataset.data[c.dataIndex].v as number; let a = (value - min) / max; if (value !== 0) { // 0でない限りは完全に不可視にはしない @@ -195,7 +194,7 @@ async function renderChart() { font: { size: 9, }, - callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value], + callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value as any], }, }, }, diff --git a/packages/frontend/src/components/MkImageEffectorDialog.Layer.vue b/packages/frontend/src/components/MkImageEffectorDialog.Layer.vue new file mode 100644 index 0000000000..bc7e8b0946 --- /dev/null +++ b/packages/frontend/src/components/MkImageEffectorDialog.Layer.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue new file mode 100644 index 0000000000..f740002088 --- /dev/null +++ b/packages/frontend/src/components/MkImageEffectorDialog.vue @@ -0,0 +1,429 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkImageEffectorFxForm.vue b/packages/frontend/src/components/MkImageEffectorFxForm.vue new file mode 100644 index 0000000000..723b5f093e --- /dev/null +++ b/packages/frontend/src/components/MkImageEffectorFxForm.vue @@ -0,0 +1,92 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkImageFrameEditorDialog.vue b/packages/frontend/src/components/MkImageFrameEditorDialog.vue new file mode 100644 index 0000000000..0badda3db7 --- /dev/null +++ b/packages/frontend/src/components/MkImageFrameEditorDialog.vue @@ -0,0 +1,468 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts index 339e6d10f3..7da705a23f 100644 --- a/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { StoryObj } from '@storybook/vue3'; +import type { StoryObj } from '@storybook/vue3'; import { file } from '../../.storybook/fakes.js'; import MkImgPreviewDialog from './MkImgPreviewDialog.vue'; export const Default = { diff --git a/packages/frontend/src/components/MkImgPreviewDialog.vue b/packages/frontend/src/components/MkImgPreviewDialog.vue index 3e6e4e0ec9..530b6c45db 100644 --- a/packages/frontend/src/components/MkImgPreviewDialog.vue +++ b/packages/frontend/src/components/MkImgPreviewDialog.vue @@ -11,15 +11,16 @@ SPDX-License-Identifier: AGPL-3.0-only @close="close" @esc="close" @click="close" + @closed="emit('closed')" >
- +
+ + diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 90391005bc..368fa5be27 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -9,31 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - - - - - - - - - - - - - - - - - - - - - + +
@@ -43,13 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - - - - +
@@ -84,12 +55,13 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue index eba8a73aec..fa22a150c3 100644 --- a/packages/frontend/src/components/MkInstanceTicker.vue +++ b/packages/frontend/src/components/MkInstanceTicker.vue @@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/packages/frontend/src/components/MkMarqueeText.vue b/packages/frontend/src/components/MkMarqueeText.vue new file mode 100644 index 0000000000..a2c365afe9 --- /dev/null +++ b/packages/frontend/src/components/MkMarqueeText.vue @@ -0,0 +1,89 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index b7052ad918..efcbf26a29 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only @contextmenu.stop @keydown.stop > - diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 3f8d92a61d..585a628a96 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -52,9 +52,9 @@ SPDX-License-Identifier: AGPL-3.0-only {{ item.label }} @@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts index 19e4eea733..f2ce55acc4 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts +++ b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts @@ -25,7 +25,7 @@ export type MkSystemWebhookResult = { }; export async function showSystemWebhookEditorDialog(props: MkSystemWebhookEditorProps): Promise { - const { result } = await new Promise<{ result: MkSystemWebhookResult | null }>(async resolve => { + const { result } = await new Promise<{ result: MkSystemWebhookResult | null }>(resolve => { const { dispose } = os.popup( defineAsyncComponent(() => import('@/components/MkSystemWebhookEditor.vue')), props, diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index 86e755a3c3..1536b14455 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -245,7 +245,7 @@ onMounted(async () => { secret.value = res.secret; isActive.value = res.isActive; for (const ev of Object.keys(events.value)) { - events.value[ev] = res.on.includes(ev as SystemWebhookEventType); + events.value[ev as SystemWebhookEventType] = res.on.includes(ev as SystemWebhookEventType); } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (ex: any) { diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue index f557ffa5dc..d8ae52482e 100644 --- a/packages/frontend/src/components/MkTab.vue +++ b/packages/frontend/src/components/MkTab.vue @@ -3,76 +3,85 @@ SPDX-FileCopyrightText: syuilo and misskey-project SPDX-License-Identifier: AGPL-3.0-only --> + + - diff --git a/packages/frontend/src/components/MkTabs.vue b/packages/frontend/src/components/MkTabs.vue index a1f30100d0..a6342ec2e1 100644 --- a/packages/frontend/src/components/MkTabs.vue +++ b/packages/frontend/src/components/MkTabs.vue @@ -4,12 +4,20 @@ SPDX-License-Identifier: AGPL-3.0-only --> - diff --git a/packages/frontend/src/components/MkTl.vue b/packages/frontend/src/components/MkTl.vue index 95cc4d2a2a..8e24294c22 100644 --- a/packages/frontend/src/components/MkTl.vue +++ b/packages/frontend/src/components/MkTl.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -21,16 +21,23 @@ SPDX-License-Identifier: AGPL-3.0-only
- + + diff --git a/packages/frontend/src/components/MkUploaderItems.vue b/packages/frontend/src/components/MkUploaderItems.vue new file mode 100644 index 0000000000..51f7ac2d09 --- /dev/null +++ b/packages/frontend/src/components/MkUploaderItems.vue @@ -0,0 +1,203 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 20dab6f028..7c0c06398b 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -44,8 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- -
+ +
@@ -91,9 +91,10 @@ import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { deviceKind } from '@/utility/device-kind.js'; import MkButton from '@/components/MkButton.vue'; -import { transformPlayerUrl } from '@/utility/player-url-transform.js'; +import { transformPlayerUrl } from '@/utility/url-preview.js'; import { store } from '@/store.js'; import { prefer } from '@/preferences.js'; +import { maybeMakeRelative } from '@@/js/url.js'; type SummalyResult = Awaited>; @@ -111,7 +112,8 @@ const props = withDefaults(defineProps<{ const MOBILE_THRESHOLD = 500; const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); -const self = props.url.startsWith(local); +const maybeRelativeUrl = maybeMakeRelative(props.url, local); +const self = maybeRelativeUrl !== props.url; const attr = self ? 'to' : 'href'; const target = self ? null : '_blank'; const fetching = ref(true); diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue index fd36d6a82b..09558b0319 100644 --- a/packages/frontend/src/components/MkUrlPreviewPopup.vue +++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue @@ -20,7 +20,7 @@ import { prefer } from '@/preferences.js'; const props = defineProps<{ showing: boolean; url: string; - source: HTMLElement; + anchorElement: HTMLElement; }>(); const emit = defineEmits<{ @@ -32,9 +32,9 @@ const top = ref(0); const left = ref(0); onMounted(() => { - const rect = props.source.getBoundingClientRect(); - const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.scrollX; - const y = rect.top + props.source.offsetHeight + window.scrollY; + const rect = props.anchorElement.getBoundingClientRect(); + const x = Math.max((rect.left + (props.anchorElement.offsetWidth / 2)) - (300 / 2), 6) + window.scrollX; + const y = rect.top + props.anchorElement.offsetHeight + window.scrollY; top.value = y; left.value = x; diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue index 34e86444ad..5e16460104 100644 --- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -22,18 +22,26 @@ SPDX-License-Identifier: AGPL-3.0-only - + - - - - - + - - - {{ i18n.ts._announcement.needConfirmationToRead }} @@ -41,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.delete }}
- +
{{ props.announcement ? i18n.ts.update : i18n.ts.create }}
@@ -50,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 17a882a3a6..9f196ac2c1 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -12,8 +12,9 @@ SPDX-License-Identifier: AGPL-3.0-only appear @afterLeave="emit('closed')" >
-
-
+ +
+
{{ i18n.ts.followsYou }}
@@ -21,7 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only - + + +
@@ -31,18 +34,18 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.noAccountDescription }}
-
+
{{ i18n.ts.notes }}
{{ number(user.notesCount) }}
-
-
+ +
{{ i18n.ts.following }}
{{ number(user.followingCount) }}
-
-
+ +
{{ i18n.ts.followers }}
{{ number(user.followersCount) }}
-
+
@@ -71,7 +74,7 @@ import { getStaticImageUrl } from '@/utility/media-proxy.js'; const props = defineProps<{ showing: boolean; - q: string; + q: string | Misskey.entities.UserDetailed; source: HTMLElement; }>(); @@ -85,26 +88,36 @@ const zIndex = os.claimZIndex('middle'); const user = ref(null); const top = ref(0); const left = ref(0); +const error = ref(false); -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { if (user.value == null) return; const { menu, cleanup } = getUserMenu(user.value); os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup); } -onMounted(() => { +async function fetchUser() { if (typeof props.q === 'object') { user.value = props.q; + error.value = false; } else { - const query = props.q.startsWith('@') ? + const query: Misskey.entities.UsersShowRequest = props.q.startsWith('@') ? Misskey.acct.parse(props.q.substring(1)) : { userId: props.q }; + // @ts-expect-error payloadの引数側の型が正常に解決されない misskeyApi('users/show', query).then(res => { if (!props.showing) return; user.value = res; + error.value = false; + }, () => { + error.value = true; }); } +} + +onMounted(() => { + fetchUser(); const rect = props.source.getBoundingClientRect(); const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.scrollX; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue index 67a06c70db..c853eb5054 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -10,10 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -22,10 +22,10 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -34,21 +34,19 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue new file mode 100644 index 0000000000..cadf9ba522 --- /dev/null +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -0,0 +1,460 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue new file mode 100644 index 0000000000..292b4010ff --- /dev/null +++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue @@ -0,0 +1,174 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index 44f6921a85..a27613c24c 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -4,37 +4,32 @@ SPDX-License-Identifier: AGPL-3.0-only --> `; +} + +globalThis.addEventListener('install', (ev) => { + // 次の問題が発生するため、ServiceWorkerAutoPreload をオプトアウトする必要がある + // https://issues.chromium.org/issues/466790291 + if ('addRoutes' in ev) { + // doc: https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes + // @ts-expect-error 実験的なAPIなので型定義がない + ev.addRoutes({ + condition: { + // doc: https://developer.mozilla.org/ja/docs/Web/API/URLPattern + // @ts-expect-error 実験的なAPIなので型定義がない + urlPattern: new URLPattern({}), + }, + source: 'fetch-event', + }); + } }); globalThis.addEventListener('activate', ev => { @@ -28,17 +88,6 @@ globalThis.addEventListener('activate', ev => { ); }); -async function offlineContentHTML() { - const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial>; - const messages = { - title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server', - header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server', - reload: i18n.ts?.reload ?? 'Reload', - }; - - return `${messages.title}
${messages.header}
v${_VERSION_}
`; -} - globalThis.addEventListener('fetch', ev => { let isHTMLRequest = false; if (ev.request.headers.get('sec-fetch-dest') === 'document') { @@ -50,18 +99,7 @@ globalThis.addEventListener('fetch', ev => { } if (!isHTMLRequest) return; - ev.respondWith( - fetch(ev.request) - .catch(async () => { - const html = await offlineContentHTML(); - return new Response(html, { - status: 200, - headers: { - 'content-type': 'text/html', - }, - }); - }), - ); + ev.respondWith(respondToNavigation(ev.request)); }); globalThis.addEventListener('push', ev => { diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json index 2712475a37..9732a438ce 100644 --- a/packages/sw/tsconfig.json +++ b/packages/sw/tsconfig.json @@ -19,7 +19,6 @@ "experimentalDecorators": true, "resolveJsonModule": true, "isolatedModules": true, - "baseUrl": ".", "paths": { "@/*": ["./src/*"], "@@/*": ["../frontend-shared/*"] @@ -28,6 +27,7 @@ "./node_modules/@types", "./src/@types" ], + "libReplacement": true, "lib": [ "esnext", "webworker" diff --git a/patches/.gitkeep b/patches/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 546a08b367..4b8cf2a9e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,86 +5,84 @@ settings: excludeLinksFromLockfile: false overrides: - chokidar: 4.0.3 + chokidar: 5.0.0 lodash: 4.17.21 '@aiscript-dev/aiscript-languageserver': '-' -patchedDependencies: - re2: - hash: 018babd22b7ce951bcd10d6246f1e541a7ac7ba212f7fa8985e774ece67d08e1 - path: scripts/dependency-patches/re2.patch - importers: .: dependencies: cssnano: - specifier: 7.0.6 - version: 7.0.6(postcss@8.5.3) + specifier: 7.1.2 + version: 7.1.2(postcss@8.5.6) esbuild: - specifier: 0.25.0 - version: 0.25.0 + specifier: 0.27.2 + version: 0.27.2 execa: - specifier: 9.5.2 - version: 9.5.2 - fast-glob: - specifier: 3.3.3 - version: 3.3.3 - glob: - specifier: 11.0.1 - version: 11.0.1 + specifier: 9.6.1 + version: 9.6.1 ignore-walk: - specifier: 7.0.0 - version: 7.0.0 + specifier: 8.0.0 + version: 8.0.0 js-yaml: - specifier: 4.1.0 - version: 4.1.0 + specifier: 4.1.1 + version: 4.1.1 postcss: - specifier: 8.5.3 - version: 8.5.3 + specifier: 8.5.6 + version: 8.5.6 tar: - specifier: 7.4.3 - version: 7.4.3 + specifier: 7.5.2 + version: 7.5.2 terser: - specifier: 5.39.0 - version: 5.39.0 - typescript: - specifier: 5.8.2 - version: 5.8.2 + specifier: 5.44.1 + version: 5.44.1 devDependencies: + '@eslint/js': + specifier: 9.39.2 + version: 9.39.2 '@misskey-dev/eslint-plugin': - specifier: 2.1.0 - version: 2.1.0(@eslint/compat@1.1.1)(@stylistic/eslint-plugin@2.13.0(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0))(eslint@9.22.0)(globals@16.0.0) + specifier: 2.2.0 + version: 2.2.0(@eslint/compat@1.4.0(eslint@9.39.2))(@eslint/js@9.39.2)(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2)(globals@16.5.0) + '@types/js-yaml': + specifier: 4.0.9 + version: 4.0.9 '@types/node': - specifier: 22.13.10 - version: 22.13.10 + specifier: 24.10.4 + version: 24.10.4 '@typescript-eslint/eslint-plugin': - specifier: 8.26.0 - version: 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.26.0 - version: 8.26.0(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript/native-preview': + specifier: 7.0.0-dev.20251226.1 + version: 7.0.0-dev.20251226.1 cross-env: - specifier: 7.0.3 - version: 7.0.3 + specifier: 10.1.0 + version: 10.1.0 cypress: - specifier: 14.1.0 - version: 14.1.0 + specifier: 15.8.1 + version: 15.8.1 eslint: - specifier: 9.22.0 - version: 9.22.0 + specifier: 9.39.2 + version: 9.39.2 globals: - specifier: 16.0.0 - version: 16.0.0 + specifier: 16.5.0 + version: 16.5.0 ncp: specifier: 2.0.0 version: 2.0.0 pnpm: - specifier: 10.6.1 - version: 10.6.1 + specifier: 10.27.0 + version: 10.27.0 start-server-and-test: - specifier: 2.0.10 - version: 2.0.10 + specifier: 2.1.3 + version: 2.1.3 + typescript: + specifier: 5.9.3 + version: 5.9.3 optionalDependencies: '@tensorflow/tfjs-core': specifier: 4.22.0 @@ -93,86 +91,80 @@ importers: packages/backend: dependencies: '@aws-sdk/client-s3': - specifier: 3.782.0 - version: 3.782.0 + specifier: 3.958.0 + version: 3.958.0 '@aws-sdk/lib-storage': - specifier: 3.782.0 - version: 3.782.0(@aws-sdk/client-s3@3.782.0) + specifier: 3.958.0 + version: 3.958.0(@aws-sdk/client-s3@3.958.0) '@discordapp/twemoji': - specifier: 15.1.0 - version: 15.1.0 + specifier: 16.0.1 + version: 16.0.1 '@fastify/accepts': - specifier: 5.0.2 - version: 5.0.2 - '@fastify/cookie': - specifier: 11.0.2 - version: 11.0.2 + specifier: 5.0.4 + version: 5.0.4 '@fastify/cors': - specifier: 10.1.0 - version: 10.1.0 + specifier: 11.2.0 + version: 11.2.0 '@fastify/express': specifier: 4.0.2 version: 4.0.2 '@fastify/http-proxy': - specifier: 10.0.2 - version: 10.0.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 11.4.1 + version: 11.4.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@fastify/multipart': - specifier: 9.0.3 - version: 9.0.3 + specifier: 9.3.0 + version: 9.3.0 '@fastify/static': - specifier: 8.1.1 - version: 8.1.1 - '@fastify/view': - specifier: 10.0.2 - version: 10.0.2 + specifier: 8.3.0 + version: 8.3.0 + '@kitajs/html': + specifier: 4.2.11 + version: 4.2.11 '@misskey-dev/sharp-read-bmp': - specifier: 1.3.0 - version: 1.3.0 + specifier: 1.2.0 + version: 1.2.0 '@misskey-dev/summaly': - specifier: 5.2.0 - version: 5.2.0 + specifier: 5.2.5 + version: 5.2.5 '@napi-rs/canvas': - specifier: 0.1.69 - version: 0.1.69 + specifier: 0.1.87 + version: 0.1.87 '@nestjs/common': - specifier: 11.0.16 - version: 11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: 11.1.10 + version: 11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': - specifier: 11.0.15 - version: 11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: 11.1.10 + version: 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.10)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/testing': - specifier: 11.0.15 - version: 11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15)(@nestjs/platform-express@10.4.15) + specifier: 11.1.10 + version: 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10)(@nestjs/platform-express@11.1.10) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 '@sentry/node': - specifier: 8.55.0 - version: 8.55.0 + specifier: 10.32.1 + version: 10.32.1 '@sentry/profiling-node': - specifier: 8.55.0 - version: 8.55.0 + specifier: 10.32.1 + version: 10.32.1 '@simplewebauthn/server': - specifier: 12.0.0 - version: 12.0.0(encoding@0.1.13) + specifier: 13.2.2 + version: 13.2.2 '@sinonjs/fake-timers': - specifier: 11.3.1 - version: 11.3.1 + specifier: 15.1.0 + version: 15.1.0 '@smithy/node-http-handler': - specifier: 2.5.0 - version: 2.5.0 + specifier: 4.4.7 + version: 4.4.7 '@swc/cli': - specifier: 0.6.0 - version: 0.6.0(@swc/core@1.11.18)(chokidar@4.0.3) + specifier: 0.7.9 + version: 0.7.9(@swc/core@1.15.7)(chokidar@5.0.0) '@swc/core': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@twemoji/parser': - specifier: 15.1.1 - version: 15.1.1 - '@types/redis-info': - specifier: 3.0.3 - version: 3.0.3 + specifier: 16.0.0 + version: 16.0.0 accepts: specifier: 1.3.8 version: 1.3.8 @@ -186,122 +178,101 @@ importers: specifier: 0.5.0 version: 0.5.0 bcryptjs: - specifier: 2.4.3 - version: 2.4.3 + specifier: 3.0.3 + version: 3.0.3 blurhash: specifier: 2.0.5 version: 2.0.5 body-parser: - specifier: 1.20.3 - version: 1.20.3 + specifier: 2.2.1 + version: 2.2.1 bullmq: - specifier: 5.48.1 - version: 5.48.1 + specifier: 5.66.3 + version: 5.66.3 cacheable-lookup: specifier: 7.0.0 version: 7.0.0 - cbor: - specifier: 9.0.2 - version: 9.0.2 chalk: - specifier: 5.4.1 - version: 5.4.1 + specifier: 5.6.2 + version: 5.6.2 chalk-template: - specifier: 1.1.0 - version: 1.1.0 + specifier: 1.1.2 + version: 1.1.2 chokidar: - specifier: 4.0.3 - version: 4.0.3 - cli-highlight: - specifier: 2.1.11 - version: 2.1.11 + specifier: 5.0.0 + version: 5.0.0 color-convert: - specifier: 2.0.1 - version: 2.0.1 + specifier: 3.1.3 + version: 3.1.3 content-disposition: - specifier: 0.5.4 - version: 0.5.4 + specifier: 1.0.1 + version: 1.0.1 date-fns: - specifier: 2.30.0 - version: 2.30.0 + specifier: 4.1.0 + version: 4.1.0 deep-email-validator: specifier: 0.1.21 version: 0.1.21 fastify: - specifier: 5.2.2 - version: 5.2.2 + specifier: 5.6.2 + version: 5.6.2 fastify-raw-body: specifier: 5.0.0 version: 5.0.0 feed: - specifier: 4.2.2 - version: 4.2.2 + specifier: 5.1.0 + version: 5.1.0 file-type: - specifier: 19.6.0 - version: 19.6.0 + specifier: 21.2.0 + version: 21.2.0 fluent-ffmpeg: specifier: 2.1.3 version: 2.1.3 form-data: - specifier: 4.0.2 - version: 4.0.2 + specifier: 4.0.5 + version: 4.0.5 got: - specifier: 14.4.7 - version: 14.4.7 - happy-dom: - specifier: 16.8.1 - version: 16.8.1 + specifier: 14.6.5 + version: 14.6.5 hpagent: specifier: 1.2.0 version: 1.2.0 - htmlescape: - specifier: 1.1.1 - version: 1.1.1 http-link-header: specifier: 1.1.3 version: 1.1.3 + i18n: + specifier: workspace:* + version: link:../i18n ioredis: - specifier: 5.6.0 - version: 5.6.0 + specifier: 5.8.2 + version: 5.8.2 ip-cidr: specifier: 4.0.2 version: 4.0.2 ipaddr.js: - specifier: 2.2.0 - version: 2.2.0 + specifier: 2.3.0 + version: 2.3.0 is-svg: - specifier: 5.1.0 - version: 5.1.0 - js-yaml: - specifier: 4.1.0 - version: 4.1.0 - jsdom: - specifier: 26.0.0 - version: 26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5) + specifier: 6.1.0 + version: 6.1.0 json5: specifier: 2.2.3 version: 2.2.3 jsonld: - specifier: 8.3.3 - version: 8.3.3(web-streams-polyfill@4.0.0) - jsrsasign: - specifier: 11.1.0 - version: 11.1.0 + specifier: 9.0.0 + version: 9.0.0 juice: - specifier: 11.0.1 - version: 11.0.1 + specifier: 11.0.3 + version: 11.0.3 meilisearch: - specifier: 0.49.0 - version: 0.49.0 + specifier: 0.54.0 + version: 0.54.0 mfm-js: - specifier: 0.24.0 - version: 0.24.0 - microformats-parser: - specifier: 2.0.2 - version: 2.0.2 + specifier: 0.25.0 + version: 0.25.0 mime-types: - specifier: 2.1.35 - version: 2.1.35 + specifier: 3.0.2 + version: 3.0.2 misskey-js: specifier: workspace:* version: link:../misskey-js @@ -312,26 +283,26 @@ importers: specifier: workspace:* version: link:../misskey-reversi ms: - specifier: 3.0.0-canary.1 - version: 3.0.0-canary.1 + specifier: 3.0.0-canary.202508261828 + version: 3.0.0-canary.202508261828 nanoid: - specifier: 5.1.5 - version: 5.1.5 + specifier: 5.1.6 + version: 5.1.6 nested-property: specifier: 4.0.0 version: 4.0.0 node-fetch: specifier: 3.3.2 version: 3.3.2 + node-html-parser: + specifier: 7.0.1 + version: 7.0.1 nodemailer: - specifier: 6.10.0 - version: 6.10.0 + specifier: 7.0.12 + version: 7.0.12 nsfwjs: specifier: 4.2.0 version: 4.2.0(@tensorflow/tfjs@4.22.0(encoding@0.1.13)(seedrandom@3.0.5))(buffer@6.0.3) - oauth: - specifier: 0.10.2 - version: 0.10.2 oauth2orize: specifier: 1.12.0 version: 1.12.0 @@ -342,26 +313,20 @@ importers: specifier: 0.0.14 version: 0.0.14 otpauth: - specifier: 9.4.0 - version: 9.4.0 - parse5: - specifier: 7.2.1 - version: 7.2.1 + specifier: 9.4.1 + version: 9.4.1 pg: - specifier: 8.14.1 - version: 8.14.1 + specifier: 8.16.3 + version: 8.16.3 pkce-challenge: - specifier: 4.1.0 - version: 4.1.0 + specifier: 5.0.1 + version: 5.0.1 probe-image-size: specifier: 7.2.3 version: 7.2.3 promise-limit: specifier: 2.7.0 version: 2.7.0 - pug: - specifier: 3.0.3 - version: 3.0.3 qrcode: specifier: 1.5.4 version: 1.5.4 @@ -372,14 +337,8 @@ importers: specifier: 3.4.1 version: 3.4.1 re2: - specifier: 1.21.4 - version: 1.21.4(patch_hash=018babd22b7ce951bcd10d6246f1e541a7ac7ba212f7fa8985e774ece67d08e1) - redis-info: - specifier: 3.1.0 - version: 3.1.0 - redis-lock: - specifier: 0.1.4 - version: 0.1.4 + specifier: 1.23.0 + version: 1.23.0 reflect-metadata: specifier: 0.2.2 version: 0.2.2 @@ -393,14 +352,17 @@ importers: specifier: 7.8.2 version: 7.8.2 sanitize-html: - specifier: 2.15.0 - version: 2.15.0 + specifier: 2.17.0 + version: 2.17.0 secure-json-parse: - specifier: 3.0.2 - version: 3.0.2 + specifier: 4.1.0 + version: 4.1.0 + semver: + specifier: 7.7.3 + version: 7.7.3 sharp: - specifier: 0.34.1 - version: 0.34.1 + specifier: 0.33.5 + version: 0.33.5 slacc: specifier: 0.0.10 version: 0.0.10 @@ -411,29 +373,23 @@ importers: specifier: 2.1.0 version: 2.1.0 systeminformation: - specifier: 5.25.11 - version: 5.25.11 + specifier: 5.28.1 + version: 5.28.1 tinycolor2: specifier: 1.6.0 version: 1.6.0 tmp: - specifier: 0.2.3 - version: 0.2.3 + specifier: 0.2.5 + version: 0.2.5 tsc-alias: - specifier: 1.8.15 - version: 1.8.15 - tsconfig-paths: - specifier: 4.2.0 - version: 4.2.0 + specifier: 1.8.16 + version: 1.8.16 typeorm: - specifier: 0.3.22 - version: 0.3.22(ioredis@5.6.0)(pg@8.14.1)(reflect-metadata@0.2.2) - typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 0.3.28 + version: 0.3.28(ioredis@5.8.2)(pg@8.16.3) ulid: - specifier: 2.4.0 - version: 2.4.0 + specifier: 3.0.2 + version: 3.0.2 vary: specifier: 1.1.2 version: 1.1.2 @@ -441,8 +397,8 @@ importers: specifier: 3.6.7 version: 3.6.7 ws: - specifier: 8.18.1 - version: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 8.18.3 + version: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) xev: specifier: 3.0.2 version: 3.0.2 @@ -450,75 +406,60 @@ importers: '@jest/globals': specifier: 29.7.0 version: 29.7.0 + '@kitajs/ts-html-plugin': + specifier: 4.1.3 + version: 4.1.3(@kitajs/html@4.2.11)(typescript@5.9.3) '@nestjs/platform-express': - specifier: 10.4.15 - version: 10.4.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15) + specifier: 11.1.10 + version: 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10) '@sentry/vue': - specifier: 9.12.0 - version: 9.12.0(vue@3.5.13(typescript@5.8.3)) + specifier: 10.32.1 + version: 10.32.1(vue@3.5.26(typescript@5.9.3)) '@simplewebauthn/types': specifier: 12.0.0 version: 12.0.0 '@swc/jest': - specifier: 0.2.37 - version: 0.2.37(@swc/core@1.11.18) + specifier: 0.2.39 + version: 0.2.39(@swc/core@1.15.7) '@types/accepts': specifier: 1.3.7 version: 1.3.7 '@types/archiver': - specifier: 6.0.3 - version: 6.0.3 - '@types/bcryptjs': - specifier: 2.4.6 - version: 2.4.6 + specifier: 7.0.0 + version: 7.0.0 '@types/body-parser': - specifier: 1.19.5 - version: 1.19.5 + specifier: 1.19.6 + version: 1.19.6 '@types/color-convert': specifier: 2.0.4 version: 2.0.4 '@types/content-disposition': - specifier: 0.5.8 - version: 0.5.8 + specifier: 0.5.9 + version: 0.5.9 '@types/fluent-ffmpeg': - specifier: 2.1.27 - version: 2.1.27 - '@types/htmlescape': - specifier: 1.1.3 - version: 1.1.3 + specifier: 2.1.28 + version: 2.1.28 '@types/http-link-header': specifier: 1.0.7 version: 1.0.7 '@types/jest': specifier: 29.5.14 version: 29.5.14 - '@types/js-yaml': - specifier: 4.0.9 - version: 4.0.9 - '@types/jsdom': - specifier: 21.1.7 - version: 21.1.7 '@types/jsonld': specifier: 1.5.15 version: 1.5.15 - '@types/jsrsasign': - specifier: 10.5.15 - version: 10.5.15 '@types/mime-types': - specifier: 2.1.4 - version: 2.1.4 + specifier: 3.0.1 + version: 3.0.1 '@types/ms': - specifier: 0.7.34 - version: 0.7.34 + specifier: 2.1.0 + version: 2.1.0 '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@types/nodemailer': - specifier: 6.4.17 - version: 6.4.17 - '@types/oauth': - specifier: 0.9.6 - version: 0.9.6 + specifier: 7.0.4 + version: 7.0.4 '@types/oauth2orize': specifier: 1.11.5 version: 1.11.5 @@ -526,14 +467,11 @@ importers: specifier: 0.1.2 version: 0.1.2 '@types/pg': - specifier: 8.11.11 - version: 8.11.11 - '@types/pug': - specifier: 2.0.10 - version: 2.0.10 + specifier: 8.16.0 + version: 8.16.0 '@types/qrcode': - specifier: 1.5.5 - version: 1.5.5 + specifier: 1.5.6 + version: 1.5.6 '@types/random-seed': specifier: 0.3.5 version: 0.3.5 @@ -544,17 +482,20 @@ importers: specifier: 1.0.7 version: 1.0.7 '@types/sanitize-html': - specifier: 2.15.0 - version: 2.15.0 + specifier: 2.16.0 + version: 2.16.0 '@types/semver': - specifier: 7.7.0 - version: 7.7.0 + specifier: 7.7.1 + version: 7.7.1 '@types/simple-oauth2': - specifier: 5.0.7 - version: 5.0.7 + specifier: 5.0.8 + version: 5.0.8 '@types/sinonjs__fake-timers': - specifier: 8.1.5 - version: 8.1.5 + specifier: 15.0.1 + version: 15.0.1 + '@types/supertest': + specifier: 6.0.3 + version: 6.0.3 '@types/tinycolor2': specifier: 1.4.6 version: 1.4.6 @@ -571,78 +512,93 @@ importers: specifier: 8.18.1 version: 8.18.1 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) aws-sdk-client-mock: specifier: 4.1.0 version: 4.1.0 + cbor: + specifier: 10.0.11 + version: 10.0.11 cross-env: - specifier: 7.0.3 - version: 7.0.3 + specifier: 10.1.0 + version: 10.1.0 + esbuild-plugin-swc: + specifier: 1.0.1 + version: 1.0.1 eslint-plugin-import: - specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0) + specifier: 2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) execa: - specifier: 8.0.1 - version: 8.0.1 + specifier: 9.6.1 + version: 9.6.1 fkill: - specifier: 9.0.0 - version: 9.0.0 + specifier: 10.0.1 + version: 10.0.1 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@22.14.0) + version: 29.7.0(@types/node@24.10.4) jest-mock: specifier: 29.7.0 version: 29.7.0 + js-yaml: + specifier: 4.1.1 + version: 4.1.1 nodemon: - specifier: 3.1.9 - version: 3.1.9 + specifier: 3.1.11 + version: 3.1.11 pid-port: - specifier: 1.0.2 - version: 1.0.2 + specifier: 2.0.0 + version: 2.0.0 simple-oauth2: specifier: 5.1.0 version: 5.1.0 + supertest: + specifier: 7.1.4 + version: 7.1.4 + vite: + specifier: 7.3.0 + version: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) optionalDependencies: '@swc/core-android-arm64': specifier: 1.3.11 version: 1.3.11 '@swc/core-darwin-arm64': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-darwin-x64': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-freebsd-x64': specifier: 1.3.11 version: 1.3.11 '@swc/core-linux-arm-gnueabihf': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-linux-arm64-gnu': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-linux-arm64-musl': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-linux-x64-gnu': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-linux-x64-musl': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-win32-arm64-msvc': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-win32-ia32-msvc': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@swc/core-win32-x64-msvc': - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.15.7 + version: 1.15.7 '@tensorflow/tfjs': specifier: 4.22.0 version: 4.22.0(encoding@0.1.13)(seedrandom@3.0.5) @@ -650,8 +606,8 @@ importers: specifier: 4.22.0 version: 4.22.0(encoding@0.1.13)(seedrandom@3.0.5) bufferutil: - specifier: 4.0.9 - version: 4.0.9 + specifier: 4.1.0 + version: 4.1.0 slacc-android-arm-eabi: specifier: 0.0.10 version: 0.0.10 @@ -692,8 +648,8 @@ importers: specifier: 0.0.10 version: 0.0.10 utf-8-validate: - specifier: 6.0.5 - version: 6.0.5 + specifier: 6.0.6 + version: 6.0.6 packages/frontend: dependencies: @@ -701,119 +657,125 @@ importers: specifier: 1.1.0 version: 1.1.0 '@discordapp/twemoji': - specifier: 15.1.0 - version: 15.1.0 + specifier: 16.0.1 + version: 16.0.1 '@github/webauthn-json': specifier: 2.1.1 version: 2.1.1 '@mcaptcha/vanilla-glue': - specifier: 0.1.0-alpha-3 - version: 0.1.0-alpha-3 + specifier: 0.1.0-rc2 + version: 0.1.0-rc2(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@misskey-dev/browser-image-resizer': specifier: 2024.1.0 version: 2024.1.0 '@rollup/plugin-json': specifier: 6.1.0 - version: 6.1.0(rollup@4.39.0) + version: 6.1.0(rollup@4.54.0) '@rollup/plugin-replace': - specifier: 6.0.2 - version: 6.0.2(rollup@4.39.0) + specifier: 6.0.3 + version: 6.0.3(rollup@4.54.0) '@rollup/pluginutils': - specifier: 5.1.4 - version: 5.1.4(rollup@4.39.0) + specifier: 5.3.0 + version: 5.3.0(rollup@4.54.0) '@sentry/vue': - specifier: 9.12.0 - version: 9.12.0(vue@3.5.13(typescript@5.8.3)) + specifier: 10.32.1 + version: 10.32.1(vue@3.5.26(typescript@5.9.3)) '@syuilo/aiscript': - specifier: 0.19.0 - version: 0.19.0 - '@tabler/icons-webfont': - specifier: 3.31.0 - version: 3.31.0 + specifier: 1.2.1 + version: 1.2.1 + '@syuilo/aiscript-0-19-0': + specifier: npm:@syuilo/aiscript@^0.19.0 + version: '@syuilo/aiscript@0.19.0' '@twemoji/parser': - specifier: 15.1.1 - version: 15.1.1 + specifier: 16.0.0 + version: 16.0.0 '@vitejs/plugin-vue': - specifier: 5.2.3 - version: 5.2.3(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.3)) - '@vue/compiler-sfc': - specifier: 3.5.13 - version: 3.5.13 + specifier: 6.0.3 + version: 6.0.3(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3)) aiscript-vscode: - specifier: github:aiscript-dev/aiscript-vscode#v0.1.15 - version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7 + specifier: github:aiscript-dev/aiscript-vscode#v0.1.16 + version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/1dc7f60cda78d030dadfc518a33c472202b2ef67 analytics: - specifier: 0.8.16 - version: 0.8.16(@types/dlv@1.1.5) - astring: - specifier: 1.9.0 - version: 1.9.0 + specifier: 0.8.19 + version: 0.8.19(@types/dlv@1.1.5) broadcast-channel: - specifier: 7.1.0 - version: 7.1.0 + specifier: 7.2.0 + version: 7.2.0 buraha: specifier: 0.0.1 version: 0.0.1 canvas-confetti: - specifier: 1.9.3 - version: 1.9.3 + specifier: 1.9.4 + version: 1.9.4 chart.js: - specifier: 4.4.8 - version: 4.4.8 + specifier: 4.5.1 + version: 4.5.1 chartjs-adapter-date-fns: specifier: 3.0.0 - version: 3.0.0(chart.js@4.4.8)(date-fns@4.1.0) + version: 3.0.0(chart.js@4.5.1)(date-fns@4.1.0) chartjs-chart-matrix: - specifier: 2.1.1 - version: 2.1.1(chart.js@4.4.8) + specifier: 3.0.0 + version: 3.0.0(chart.js@4.5.1) chartjs-plugin-gradient: specifier: 0.6.1 - version: 0.6.1(chart.js@4.4.8) + version: 0.6.1(chart.js@4.5.1) chartjs-plugin-zoom: specifier: 2.2.0 - version: 2.2.0(chart.js@4.4.8) + version: 2.2.0(chart.js@4.5.1) chromatic: - specifier: 11.28.0 - version: 11.28.0 + specifier: 13.3.4 + version: 13.3.4 compare-versions: specifier: 6.1.1 version: 6.1.1 cropperjs: - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.1.0 + version: 2.1.0 date-fns: specifier: 4.1.0 version: 4.1.0 - estree-walker: - specifier: 3.0.3 - version: 3.0.3 eventemitter3: specifier: 5.0.1 version: 5.0.1 + execa: + specifier: 9.6.1 + version: 9.6.1 + exifreader: + specifier: 4.33.1 + version: 4.33.1 frontend-shared: specifier: workspace:* version: link:../frontend-shared + i18n: + specifier: workspace:* + version: link:../i18n + icons-subsetter: + specifier: workspace:* + version: link:../icons-subsetter idb-keyval: - specifier: 6.2.1 - version: 6.2.1 + specifier: 6.2.2 + version: 6.2.2 insert-text-at-cursor: specifier: 0.3.0 version: 0.3.0 + ios-haptics: + specifier: 0.1.4 + version: 0.1.4 is-file-animated: specifier: 1.0.2 version: 1.0.2 json5: specifier: 2.2.3 version: 2.2.3 - magic-string: - specifier: 0.30.17 - version: 0.30.17 matter-js: specifier: 0.20.0 version: 0.20.0 + mediabunny: + specifier: 1.27.2 + version: 1.27.2 mfm-js: - specifier: 0.24.0 - version: 0.24.0 + specifier: 0.25.0 + version: 0.25.0 misskey-bubble-game: specifier: workspace:* version: link:../misskey-bubble-game @@ -832,277 +794,299 @@ importers: punycode.js: specifier: 2.3.1 version: 2.3.1 + qr-code-styling: + specifier: 1.9.2 + version: 1.9.2 + qr-scanner: + specifier: 1.4.2 + version: 1.4.2 rollup: - specifier: 4.39.0 - version: 4.39.0 + specifier: 4.54.0 + version: 4.54.0 sanitize-html: - specifier: 2.15.0 - version: 2.15.0 + specifier: 2.17.0 + version: 2.17.0 sass: - specifier: 1.86.3 - version: 1.86.3 + specifier: 1.97.1 + version: 1.97.1 shiki: - specifier: 3.2.2 - version: 3.2.2 - strict-event-emitter-types: - specifier: 2.0.0 - version: 2.0.0 + specifier: 3.20.0 + version: 3.20.0 textarea-caret: specifier: 3.1.0 version: 3.1.0 three: - specifier: 0.175.0 - version: 0.175.0 + specifier: 0.182.0 + version: 0.182.0 throttle-debounce: specifier: 5.0.2 version: 5.0.2 tinycolor2: specifier: 1.6.0 version: 1.6.0 - tsc-alias: - specifier: 1.8.15 - version: 1.8.15 - tsconfig-paths: - specifier: 4.2.0 - version: 4.2.0 - typescript: - specifier: 5.8.3 - version: 5.8.3 - uuid: - specifier: 11.1.0 - version: 11.1.0 v-code-diff: specifier: 1.13.1 - version: 1.13.1(vue@3.5.13(typescript@5.8.3)) + version: 1.13.1(vue@3.5.26(typescript@5.9.3)) vite: - specifier: 6.3.1 - version: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + specifier: 7.3.0 + version: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) vue: - specifier: 3.5.13 - version: 3.5.13(typescript@5.8.3) - vuedraggable: - specifier: next - version: 4.1.0(vue@3.5.13(typescript@5.8.3)) + specifier: 3.5.26 + version: 3.5.26(typescript@5.9.3) wanakana: specifier: 5.3.1 version: 5.3.1 devDependencies: '@misskey-dev/summaly': - specifier: 5.2.0 - version: 5.2.0 - '@storybook/addon-actions': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 5.2.5 + version: 5.2.5 '@storybook/addon-essentials': - specifier: 8.6.12 - version: 8.6.12(@types/react@18.0.28)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(@types/react@19.2.2)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/addon-interactions': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/addon-links': - specifier: 8.6.12 - version: 8.6.12(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 10.1.10 + version: 10.1.10(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/addon-mdx-gfm': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/addon-storysource': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/blocks': - specifier: 8.6.12 - version: 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/components': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/core-events': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/manager-api': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/preview-api': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/react': - specifier: 8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.3) + specifier: 10.1.10 + version: 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(typescript@5.9.3) '@storybook/react-vite': - specifier: 8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.39.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.3)(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) + specifier: 10.1.10 + version: 10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) '@storybook/test': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/theming': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/types': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + specifier: 8.6.15 + version: 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) '@storybook/vue3': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.3)) + specifier: 10.1.10 + version: 10.1.10(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vue@3.5.26(typescript@5.9.3)) '@storybook/vue3-vite': - specifier: 8.6.12 - version: 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.3)) + specifier: 10.1.10 + version: 10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3)) + '@tabler/icons-webfont': + specifier: 3.35.0 + version: 3.35.0 '@testing-library/vue': specifier: 8.1.0 - version: 8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + version: 8.1.0(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)) '@types/canvas-confetti': specifier: 1.9.0 version: 1.9.0 '@types/estree': - specifier: 1.0.7 - version: 1.0.7 + specifier: 1.0.8 + version: 1.0.8 + '@types/insert-text-at-cursor': + specifier: 0.3.2 + version: 0.3.2 '@types/matter-js': - specifier: 0.19.8 - version: 0.19.8 + specifier: 0.20.2 + version: 0.20.2 '@types/micromatch': - specifier: 4.0.9 - version: 4.0.9 + specifier: 4.0.10 + version: 4.0.10 '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@types/punycode.js': specifier: npm:@types/punycode@2.1.4 version: '@types/punycode@2.1.4' '@types/sanitize-html': - specifier: 2.15.0 - version: 2.15.0 + specifier: 2.16.0 + version: 2.16.0 '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 + '@types/textarea-caret': + specifier: 3.0.4 + version: 3.0.4 '@types/throttle-debounce': specifier: 5.0.2 version: 5.0.2 '@types/tinycolor2': specifier: 1.4.6 version: 1.4.6 - '@types/ws': - specifier: 8.18.1 - version: 8.18.1 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) '@vitest/coverage-v8': - specifier: 3.1.1 - version: 3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) + specifier: 4.0.16 + version: 4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) '@vue/compiler-core': - specifier: 3.5.13 - version: 3.5.13 - '@vue/runtime-core': - specifier: 3.5.13 - version: 3.5.13 + specifier: 3.5.26 + version: 3.5.26 acorn: - specifier: 8.14.1 - version: 8.14.1 + specifier: 8.15.0 + version: 8.15.0 + astring: + specifier: 1.9.0 + version: 1.9.0 cross-env: - specifier: 7.0.3 - version: 7.0.3 + specifier: 10.1.0 + version: 10.1.0 cypress: - specifier: 14.3.0 - version: 14.3.0 + specifier: 15.8.1 + version: 15.8.1 eslint-plugin-import: - specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0) + specifier: 2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) eslint-plugin-vue: - specifier: 10.0.0 - version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.3(eslint@9.22.0)) - fast-glob: - specifier: 3.3.3 - version: 3.3.3 + specifier: 10.6.2 + version: 10.6.2(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)) + estree-walker: + specifier: 3.0.3 + version: 3.0.3 happy-dom: - specifier: 17.4.4 - version: 17.4.4 + specifier: 20.0.11 + version: 20.0.11 intersection-observer: specifier: 0.12.2 version: 0.12.2 + magic-string: + specifier: 0.30.21 + version: 0.30.21 micromatch: specifier: 4.0.8 version: 4.0.8 minimatch: - specifier: 10.0.1 - version: 10.0.1 + specifier: 10.1.1 + version: 10.1.1 msw: - specifier: 2.7.3 - version: 2.7.3(@types/node@22.14.0)(typescript@5.8.3) + specifier: 2.12.6 + version: 2.12.6(@types/node@24.10.4)(typescript@5.9.3) msw-storybook-addon: - specifier: 2.0.4 - version: 2.0.4(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3)) + specifier: 2.0.6 + version: 2.0.6(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3)) nodemon: - specifier: 3.1.9 - version: 3.1.9 + specifier: 3.1.11 + version: 3.1.11 prettier: - specifier: 3.5.3 - version: 3.5.3 + specifier: 3.7.4 + version: 3.7.4 react: - specifier: 19.1.0 - version: 19.1.0 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.1.0 - version: 19.1.0(react@19.1.0) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) seedrandom: specifier: 3.0.5 version: 3.0.5 start-server-and-test: - specifier: 2.0.11 - version: 2.0.11 + specifier: 2.1.3 + version: 2.1.3 storybook: - specifier: 8.6.12 - version: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + specifier: 10.1.10 + version: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.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(@storybook/blocks@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/components@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/core-events@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/manager-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/preview-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/theming@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/types@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(d02eada84d4e66c2dd831f7184cb2a49) + tsx: + specifier: 4.21.0 + version: 4.21.0 + vite-plugin-glsl: + specifier: 1.5.5 + version: 1.5.5(@rollup/pluginutils@5.3.0(rollup@4.54.0))(esbuild@0.27.2)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) vite-plugin-turbosnap: specifier: 1.0.3 version: 1.0.3 vitest: - specifier: 3.1.1 - version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + specifier: 4.0.16 + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) vitest-fetch-mock: specifier: 0.4.5 - version: 0.4.5(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) + version: 0.4.5(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) vue-component-type-helpers: - specifier: 2.2.8 - version: 2.2.8 + specifier: 3.2.1 + version: 3.2.1 vue-eslint-parser: - specifier: 10.1.3 - version: 10.1.3(eslint@9.22.0) + specifier: 10.2.0 + version: 10.2.0(eslint@9.39.2) vue-tsc: - specifier: 2.2.8 - version: 2.2.8(typescript@5.8.3) + specifier: 3.2.1 + version: 3.2.1(typescript@5.9.3) + + packages/frontend-builder: + dependencies: + estree-walker: + specifier: 3.0.3 + version: 3.0.3 + i18n: + specifier: workspace:* + version: link:../i18n + magic-string: + specifier: 0.30.21 + version: 0.30.21 + vite: + specifier: 7.3.0 + version: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + devDependencies: + '@types/estree': + specifier: 1.0.8 + version: 1.0.8 + '@types/node': + specifier: 24.10.4 + version: 24.10.4 + '@typescript-eslint/eslint-plugin': + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + rollup: + specifier: 4.54.0 + version: 4.54.0 packages/frontend-embed: dependencies: '@discordapp/twemoji': - specifier: 15.1.0 - version: 15.1.0 + specifier: 16.0.1 + version: 16.0.1 '@rollup/plugin-json': specifier: 6.1.0 - version: 6.1.0(rollup@4.39.0) + version: 6.1.0(rollup@4.54.0) '@rollup/plugin-replace': - specifier: 6.0.2 - version: 6.0.2(rollup@4.39.0) + specifier: 6.0.3 + version: 6.0.3(rollup@4.54.0) '@rollup/pluginutils': - specifier: 5.1.4 - version: 5.1.4(rollup@4.39.0) - '@tabler/icons-webfont': - specifier: 3.31.0 - version: 3.31.0 + specifier: 5.3.0 + version: 5.3.0(rollup@4.54.0) '@twemoji/parser': - specifier: 15.1.1 - version: 15.1.1 + specifier: 16.0.0 + version: 16.0.0 '@vitejs/plugin-vue': - specifier: 5.2.3 - version: 5.2.3(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.3)) - '@vue/compiler-sfc': - specifier: 3.5.13 - version: 3.5.13 - astring: - specifier: 1.9.0 - version: 1.9.0 + specifier: 6.0.3 + version: 6.0.3(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3)) buraha: specifier: 0.0.1 version: 0.0.1 @@ -1112,12 +1096,18 @@ importers: frontend-shared: specifier: workspace:* version: link:../frontend-shared + i18n: + specifier: workspace:* + version: link:../i18n + icons-subsetter: + specifier: workspace:* + version: link:../icons-subsetter json5: specifier: 2.2.3 version: 2.2.3 mfm-js: - specifier: 0.24.0 - version: 0.24.0 + specifier: 0.25.0 + version: 0.25.0 misskey-js: specifier: workspace:* version: link:../misskey-js @@ -1125,51 +1115,45 @@ importers: specifier: 2.3.1 version: 2.3.1 rollup: - specifier: 4.39.0 - version: 4.39.0 + specifier: 4.54.0 + version: 4.54.0 sass: - specifier: 1.86.3 - version: 1.86.3 + specifier: 1.97.1 + version: 1.97.1 shiki: - specifier: 3.2.2 - version: 3.2.2 + specifier: 3.20.0 + version: 3.20.0 tinycolor2: specifier: 1.6.0 version: 1.6.0 - tsc-alias: - specifier: 1.8.15 - version: 1.8.15 - tsconfig-paths: - specifier: 4.2.0 - version: 4.2.0 - typescript: - specifier: 5.8.3 - version: 5.8.3 uuid: - specifier: 11.1.0 - version: 11.1.0 + specifier: 13.0.0 + version: 13.0.0 vite: - specifier: 6.3.1 - version: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + specifier: 7.3.0 + version: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) vue: - specifier: 3.5.13 - version: 3.5.13(typescript@5.8.3) + specifier: 3.5.26 + version: 3.5.26(typescript@5.9.3) devDependencies: '@misskey-dev/summaly': - specifier: 5.2.0 - version: 5.2.0 + specifier: 5.2.5 + version: 5.2.5 + '@tabler/icons-webfont': + specifier: 3.35.0 + version: 3.35.0 '@testing-library/vue': specifier: 8.1.0 - version: 8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + version: 8.1.0(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)) '@types/estree': - specifier: 1.0.7 - version: 1.0.7 + specifier: 1.0.8 + version: 1.0.8 '@types/micromatch': - specifier: 4.0.9 - version: 4.0.9 + specifier: 4.0.10 + version: 4.0.10 '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@types/punycode.js': specifier: npm:@types/punycode@2.1.4 version: '@types/punycode@2.1.4' @@ -1180,35 +1164,32 @@ importers: specifier: 8.18.1 version: 8.18.1 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) '@vitest/coverage-v8': - specifier: 3.1.1 - version: 3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) + specifier: 4.0.16 + version: 4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) '@vue/runtime-core': - specifier: 3.5.13 - version: 3.5.13 + specifier: 3.5.26 + version: 3.5.26 acorn: - specifier: 8.14.1 - version: 8.14.1 + specifier: 8.15.0 + version: 8.15.0 cross-env: - specifier: 7.0.3 - version: 7.0.3 + specifier: 10.1.0 + version: 10.1.0 eslint-plugin-import: - specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0) + specifier: 2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) eslint-plugin-vue: - specifier: 10.0.0 - version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.3(eslint@9.22.0)) - fast-glob: - specifier: 3.3.3 - version: 3.3.3 + specifier: 10.6.2 + version: 10.6.2(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)) happy-dom: - specifier: 17.4.4 - version: 17.4.4 + specifier: 20.0.11 + version: 20.0.11 intersection-observer: specifier: 0.12.2 version: 0.12.2 @@ -1216,63 +1197,128 @@ importers: specifier: 4.0.8 version: 4.0.8 msw: - specifier: 2.7.3 - version: 2.7.3(@types/node@22.14.0)(typescript@5.8.3) + specifier: 2.12.6 + version: 2.12.6(@types/node@24.10.4)(typescript@5.9.3) nodemon: - specifier: 3.1.9 - version: 3.1.9 + specifier: 3.1.11 + version: 3.1.11 prettier: - specifier: 3.5.3 - version: 3.5.3 + specifier: 3.7.4 + version: 3.7.4 start-server-and-test: - specifier: 2.0.11 - version: 2.0.11 + specifier: 2.1.3 + version: 2.1.3 + tsx: + specifier: 4.21.0 + version: 4.21.0 vite-plugin-turbosnap: specifier: 1.0.3 version: 1.0.3 vue-component-type-helpers: - specifier: 2.2.8 - version: 2.2.8 + specifier: 3.2.1 + version: 3.2.1 vue-eslint-parser: - specifier: 10.1.3 - version: 10.1.3(eslint@9.22.0) + specifier: 10.2.0 + version: 10.2.0(eslint@9.39.2) vue-tsc: - specifier: 2.2.8 - version: 2.2.8(typescript@5.8.3) + specifier: 3.2.1 + version: 3.2.1(typescript@5.9.3) packages/frontend-shared: dependencies: + i18n: + specifier: workspace:* + version: link:../i18n misskey-js: specifier: workspace:* version: link:../misskey-js vue: - specifier: 3.5.13 - version: 3.5.13(typescript@5.8.3) + specifier: 3.5.26 + version: 3.5.26(typescript@5.9.3) devDependencies: '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) esbuild: - specifier: 0.25.2 - version: 0.25.2 + specifier: 0.27.2 + version: 0.27.2 eslint-plugin-vue: - specifier: 10.0.0 - version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.3(eslint@9.22.0)) + specifier: 10.6.2 + version: 10.6.2(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)) nodemon: - specifier: 3.1.9 - version: 3.1.9 - typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 3.1.11 + version: 3.1.11 vue-eslint-parser: - specifier: 10.1.3 - version: 10.1.3(eslint@9.22.0) + specifier: 10.2.0 + version: 10.2.0(eslint@9.39.2) + + packages/i18n: + dependencies: + js-yaml: + specifier: 4.1.1 + version: 4.1.1 + devDependencies: + '@types/js-yaml': + specifier: 4.0.9 + version: 4.0.9 + '@types/node': + specifier: 24.10.4 + version: 24.10.4 + '@typescript-eslint/eslint-plugin': + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + chokidar: + specifier: 5.0.0 + version: 5.0.0 + esbuild: + specifier: 0.27.2 + version: 0.27.2 + execa: + specifier: 9.6.1 + version: 9.6.1 + nodemon: + specifier: 3.1.11 + version: 3.1.11 + tsx: + specifier: 4.21.0 + version: 4.21.0 + + packages/icons-subsetter: + dependencies: + '@tabler/icons-webfont': + specifier: 3.35.0 + version: 3.35.0 + harfbuzzjs: + specifier: 0.4.14 + version: 0.4.14 + tsx: + specifier: 4.21.0 + version: 4.21.0 + wawoff2: + specifier: 2.0.1 + version: 2.0.1 + devDependencies: + '@types/node': + specifier: 24.10.4 + version: 24.10.4 + '@types/wawoff2': + specifier: 1.0.2 + version: 1.0.2 + '@typescript-eslint/eslint-plugin': + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) packages/misskey-bubble-game: dependencies: @@ -1287,35 +1333,29 @@ importers: version: 3.0.5 devDependencies: '@types/matter-js': - specifier: 0.19.8 - version: 0.19.8 + specifier: 0.20.2 + version: 0.20.2 '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) esbuild: - specifier: 0.25.2 - version: 0.25.2 + specifier: 0.27.2 + version: 0.27.2 execa: - specifier: 9.5.2 - version: 9.5.2 - glob: - specifier: 11.0.1 - version: 11.0.1 + specifier: 9.6.1 + version: 9.6.1 nodemon: - specifier: 3.1.9 - version: 3.1.9 - typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 3.1.11 + version: 3.1.11 packages/misskey-js: dependencies: @@ -1330,86 +1370,71 @@ importers: version: 4.4.0 devDependencies: '@microsoft/api-extractor': - specifier: 7.52.2 - version: 7.52.2(@types/node@22.13.15) - '@swc/jest': - specifier: 0.2.37 - version: 0.2.37(@swc/core@1.11.18) - '@types/jest': - specifier: 29.5.14 - version: 29.5.14 + specifier: 7.55.2 + version: 7.55.2(@types/node@24.10.4) '@types/node': - specifier: 22.13.15 - version: 22.13.15 + specifier: 24.10.4 + version: 24.10.4 '@typescript-eslint/eslint-plugin': - specifier: 8.29.0 - version: 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.0 - version: 8.29.0(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@vitest/coverage-v8': + specifier: 4.0.16 + version: 4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) esbuild: - specifier: 0.25.2 - version: 0.25.2 + specifier: 0.27.2 + version: 0.27.2 execa: - specifier: 8.0.1 - version: 8.0.1 - glob: - specifier: 11.0.1 - version: 11.0.1 - jest: - specifier: 29.7.0 - version: 29.7.0(@types/node@22.13.15) - jest-fetch-mock: - specifier: 3.0.3 - version: 3.0.3(encoding@0.1.13) - jest-websocket-mock: - specifier: 2.5.0 - version: 2.5.0 - mock-socket: - specifier: 9.3.1 - version: 9.3.1 + specifier: 9.6.1 + version: 9.6.1 ncp: specifier: 2.0.0 version: 2.0.0 nodemon: - specifier: 3.1.9 - version: 3.1.9 + specifier: 3.1.11 + version: 3.1.11 tsd: - specifier: 0.31.2 - version: 0.31.2 - typescript: - specifier: 5.8.2 - version: 5.8.2 + specifier: 0.33.0 + version: 0.33.0 + vitest: + specifier: 4.0.16 + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + vitest-websocket-mock: + specifier: 0.5.0 + version: 0.5.0(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) packages/misskey-js/generator: devDependencies: '@readme/openapi-parser': - specifier: 2.7.0 - version: 2.7.0(openapi-types@12.1.3) + specifier: 5.4.0 + version: 5.4.0(openapi-types@12.1.3) '@types/node': - specifier: 22.13.15 - version: 22.13.15 + specifier: 24.10.4 + version: 24.10.4 '@typescript-eslint/eslint-plugin': - specifier: 8.29.0 - version: 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.0 - version: 8.29.0(eslint@9.22.0)(typescript@5.8.2) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + eslint: + specifier: 9.39.2 + version: 9.39.2 openapi-types: specifier: 12.1.3 version: 12.1.3 openapi-typescript: - specifier: 6.7.6 - version: 6.7.6 + specifier: 7.10.1 + version: 7.10.1(typescript@5.9.3) ts-case-convert: specifier: 2.1.0 version: 2.1.0 tsx: - specifier: 4.19.3 - version: 4.19.3 - typescript: - specifier: 5.8.2 - version: 5.8.2 + specifier: 4.21.0 + version: 4.21.0 packages/misskey-mahjong: dependencies: @@ -1425,7 +1450,7 @@ importers: devDependencies: '@misskey-dev/eslint-plugin': specifier: 1.0.0 - version: 1.0.0(@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0)(typescript@5.3.3))(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0))(eslint@9.22.0) + version: 1.0.0(@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2)(typescript@5.3.3))(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2))(eslint@9.39.2) '@types/jest': specifier: 29.5.12 version: 29.5.12 @@ -1434,10 +1459,10 @@ importers: version: 20.11.17 '@typescript-eslint/eslint-plugin': specifier: 6.18.1 - version: 6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0)(typescript@5.3.3) + version: 6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2)(typescript@5.3.3) '@typescript-eslint/parser': specifier: 6.18.1 - version: 6.18.1(eslint@9.22.0)(typescript@5.3.3) + version: 6.18.1(eslint@9.39.2)(typescript@5.3.3) jest: specifier: 29.7.0 version: 29.7.0(@types/node@20.11.17) @@ -1446,7 +1471,7 @@ importers: version: 3.0.2 ts-jest: specifier: 29.1.2 - version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.11)(jest@29.7.0(@types/node@20.11.17))(typescript@5.3.3) + version: 29.1.2(@babel/core@7.28.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(esbuild@0.19.11)(jest@29.7.0(@types/node@20.11.17))(typescript@5.3.3) ts-jest-resolver: specifier: 2.0.1 version: 2.0.1 @@ -1461,96 +1486,104 @@ importers: version: 1.2.2 devDependencies: '@types/node': - specifier: 22.14.0 - version: 22.14.0 + specifier: 24.10.4 + version: 24.10.4 '@typescript-eslint/eslint-plugin': - specifier: 8.29.1 - version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) esbuild: - specifier: 0.25.2 - version: 0.25.2 + specifier: 0.27.2 + version: 0.27.2 execa: - specifier: 9.5.2 - version: 9.5.2 - glob: - specifier: 11.0.1 - version: 11.0.1 + specifier: 9.6.1 + version: 9.6.1 nodemon: - specifier: 3.1.9 - version: 3.1.9 - typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 3.1.11 + version: 3.1.11 packages/sw: dependencies: esbuild: - specifier: 0.25.2 - version: 0.25.2 + specifier: 0.27.2 + version: 0.27.2 + i18n: + specifier: workspace:* + version: link:../i18n idb-keyval: - specifier: 6.2.1 - version: 6.2.1 + specifier: 6.2.2 + version: 6.2.2 misskey-js: specifier: workspace:* version: link:../misskey-js devDependencies: '@typescript-eslint/parser': - specifier: 8.29.1 - version: 8.29.1(eslint@9.22.0)(typescript@5.8.3) + specifier: 8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) '@typescript/lib-webworker': specifier: npm:@types/serviceworker@0.0.74 version: '@types/serviceworker@0.0.74' eslint-plugin-import: - specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0) + specifier: 2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) nodemon: - specifier: 3.1.9 - version: 3.1.9 - typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 3.1.11 + version: 3.1.11 packages: - '@adobe/css-tools@4.4.0': - resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + '@acemir/cssom@0.9.30': + resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@analytics/cookie-utils@0.2.12': - resolution: {integrity: sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==} + '@analytics/cookie-utils@0.2.14': + resolution: {integrity: sha512-x51x2cLqvP5Fb1ydgNvTCX+SVv0ALK/yTNwp/53++yk4kLhxb850krWtQ4aASN0612oXrIGotwfmdJIttnLiPQ==} - '@analytics/core@0.12.17': - resolution: {integrity: sha512-GMxRm5Dp3Wam/w5NNvqNKMO6zWecozbVv21Kn4WhftCx6OjJI7zMlVtiLpjGjxa0RRZfVG80YhupF0Qh9XL2gw==} + '@analytics/core@0.13.2': + resolution: {integrity: sha512-ejvfoPP8TEh2hA2szMEq9c4TdeX8FAeY1j/7MxJVZjzDaq8BDHOyaAAQzTFiLMHvV0WcU2YC0smJ5Ids5Ll5ng==} - '@analytics/global-storage-utils@0.1.7': - resolution: {integrity: sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==} + '@analytics/global-storage-utils@0.1.9': + resolution: {integrity: sha512-+xm6CDnWsVOQIKkqbPRPRdYDXKk3PNgr/bCZWSI+7tEDT5PCDgI0QSBZe+FqCVkCRtTkgOrjFOY7wOM8Gq+ndA==} '@analytics/google-analytics@1.1.0': resolution: {integrity: sha512-i8uGyELMtwEUAf3GNWNLNBzhRvReDn1RUxvMdMhjUA7+GNGxPOM4kkzFfv3giQXKNxTEjfsh75kqNcscbJsuaA==} - '@analytics/localstorage-utils@0.1.10': - resolution: {integrity: sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==} + '@analytics/localstorage-utils@0.1.12': + resolution: {integrity: sha512-BL3vuZUwWgMqdkQsE0GKsED5SPLC6daI4K4LE0a/BkKv+4Cae5JLLqpO5gju2HUGOjJxIvw8U/G5EcglNY5+1w==} - '@analytics/session-storage-utils@0.0.7': - resolution: {integrity: sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==} + '@analytics/session-storage-utils@0.0.9': + resolution: {integrity: sha512-fhP9QCpyq45rZKsXaAxyz+VTmOUWljIW08CWSkFzpwOHkDM4Xy5tymc1YcWqSBBaLjHldo3HlY4qfqEIS4Aj1A==} - '@analytics/storage-utils@0.4.2': - resolution: {integrity: sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==} + '@analytics/storage-utils@0.4.4': + resolution: {integrity: sha512-873P4wDIunbOnBqADc2AhTVsLbluUv1dP6k9UrK8FIeV8WXv5+fG12HdwwaniUIxq6QLgZJfKEaCwtWSKrrV0g==} - '@analytics/type-utils@0.6.2': - resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} + '@analytics/type-utils@0.6.4': + resolution: {integrity: sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw==} - '@apidevtools/swagger-methods@3.0.2': - resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + '@apidevtools/json-schema-ref-parser@14.2.1': + resolution: {integrity: sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==} + engines: {node: '>= 20'} + peerDependencies: + '@types/json-schema': ^7.0.15 - '@asamuzakjp/css-color@2.8.3': - resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} + '@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@4.1.1': + resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + + '@asamuzakjp/dom-selector@6.7.6': + resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} @@ -1575,129 +1608,232 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.782.0': - resolution: {integrity: sha512-V6JR2JAGYQY7J8wk5un5n/ja2nfCUyyoRCF8Du8JL91NGI8i41Mdr/TzuOGwTgFl6RSXb/ge1K1jk30OH4MugQ==} + '@aws-sdk/client-s3@3.958.0': + resolution: {integrity: sha512-ol8Sw37AToBWb6PjRuT/Wu40SrrZSA0N4F7U3yTkjUNX0lirfO1VFLZ0hZtZplVJv8GNPITbiczxQ8VjxESXxg==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.782.0': - resolution: {integrity: sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg==} + '@aws-sdk/client-sesv2@3.938.0': + resolution: {integrity: sha512-GW07FQuZkW5ASm0WP+CWLetcortqup9l3+p1OlvuUN3rLBIzlWRqYd5Nf2GTS72sPbaNowE3dYJXCtwu1IlLuQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.775.0': - resolution: {integrity: sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==} + '@aws-sdk/client-sso@3.936.0': + resolution: {integrity: sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.775.0': - resolution: {integrity: sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==} + '@aws-sdk/client-sso@3.958.0': + resolution: {integrity: sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.775.0': - resolution: {integrity: sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==} + '@aws-sdk/core@3.936.0': + resolution: {integrity: sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.782.0': - resolution: {integrity: sha512-wd4KdRy2YjLsE4Y7pz00470Iip06GlRHkG4dyLW7/hFMzEO2o7ixswCWp6J2VGZVAX64acknlv2Q0z02ebjmhw==} + '@aws-sdk/core@3.957.0': + resolution: {integrity: sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.782.0': - resolution: {integrity: sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw==} + '@aws-sdk/crc64-nvme@3.957.0': + resolution: {integrity: sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.775.0': - resolution: {integrity: sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==} + '@aws-sdk/credential-provider-env@3.936.0': + resolution: {integrity: sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.782.0': - resolution: {integrity: sha512-1y1ucxTtTIGDSNSNxriQY8msinilhe9gGvQpUDYW9gboyC7WQJPDw66imy258V6osdtdi+xoHzVCbCz3WhosMQ==} + '@aws-sdk/credential-provider-env@3.957.0': + resolution: {integrity: sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.782.0': - resolution: {integrity: sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ==} + '@aws-sdk/credential-provider-http@3.936.0': + resolution: {integrity: sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==} engines: {node: '>=18.0.0'} - '@aws-sdk/lib-storage@3.782.0': - resolution: {integrity: sha512-UQYnIzpBReLko2XhDgG/rWpoHTWv4/zqUNl4XJXZRo9akLzrxGKtPrp5nJ4OLUkH3tIm1cvmI3XlSjHUW/OxWw==} + '@aws-sdk/credential-provider-http@3.957.0': + resolution: {integrity: sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.936.0': + resolution: {integrity: sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.958.0': + resolution: {integrity: sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-login@3.936.0': + resolution: {integrity: sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-login@3.958.0': + resolution: {integrity: sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.936.0': + resolution: {integrity: sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.958.0': + resolution: {integrity: sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.936.0': + resolution: {integrity: sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.957.0': + resolution: {integrity: sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.936.0': + resolution: {integrity: sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.958.0': + resolution: {integrity: sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.936.0': + resolution: {integrity: sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.958.0': + resolution: {integrity: sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/lib-storage@3.958.0': + resolution: {integrity: sha512-cd8CTiJ165ep2DKTc2PHHhVCxDn3byv10BXMGn+lkDY3KwMoatcgZ1uhFWCBuJvsCUnSExqGouJN/Q0qgjkWtg==} engines: {node: '>=18.0.0'} peerDependencies: - '@aws-sdk/client-s3': ^3.782.0 + '@aws-sdk/client-s3': ^3.958.0 - '@aws-sdk/middleware-bucket-endpoint@3.775.0': - resolution: {integrity: sha512-qogMIpVChDYr4xiUNC19/RDSw/sKoHkAhouS6Skxiy6s27HBhow1L3Z1qVYXuBmOZGSWPU0xiyZCvOyWrv9s+Q==} + '@aws-sdk/middleware-bucket-endpoint@3.957.0': + resolution: {integrity: sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-expect-continue@3.775.0': - resolution: {integrity: sha512-Apd3owkIeUW5dnk3au9np2IdW2N0zc9NjTjHiH+Mx3zqwSrc+m+ANgJVgk9mnQjMzU/vb7VuxJ0eqdEbp5gYsg==} + '@aws-sdk/middleware-expect-continue@3.957.0': + resolution: {integrity: sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.775.0': - resolution: {integrity: sha512-OmHLfRIb7IIXsf9/X/pMOlcSV3gzW/MmtPSZTkrz5jCTKzWXd7eRoyOJqewjsaC6KMAxIpNU77FoAd16jOZ21A==} + '@aws-sdk/middleware-flexible-checksums@3.957.0': + resolution: {integrity: sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.775.0': - resolution: {integrity: sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==} + '@aws-sdk/middleware-host-header@3.936.0': + resolution: {integrity: sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-location-constraint@3.775.0': - resolution: {integrity: sha512-8TMXEHZXZTFTckQLyBT5aEI8fX11HZcwZseRifvBKKpj0RZDk4F0EEYGxeNSPpUQ7n+PRWyfAEnnZNRdAj/1NQ==} + '@aws-sdk/middleware-host-header@3.957.0': + resolution: {integrity: sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.775.0': - resolution: {integrity: sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==} + '@aws-sdk/middleware-location-constraint@3.957.0': + resolution: {integrity: sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.775.0': - resolution: {integrity: sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==} + '@aws-sdk/middleware-logger@3.936.0': + resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.775.0': - resolution: {integrity: sha512-zsvcu7cWB28JJ60gVvjxPCI7ZU7jWGcpNACPiZGyVtjYXwcxyhXbYEVDSWKsSA6ERpz9XrpLYod8INQWfW3ECg==} + '@aws-sdk/middleware-logger@3.957.0': + resolution: {integrity: sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-ssec@3.775.0': - resolution: {integrity: sha512-Iw1RHD8vfAWWPzBBIKaojO4GAvQkHOYIpKdAfis/EUSUmSa79QsnXnRqsdcE0mCB0Ylj23yi+ah4/0wh9FsekA==} + '@aws-sdk/middleware-recursion-detection@3.936.0': + resolution: {integrity: sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.782.0': - resolution: {integrity: sha512-i32H2R6IItX+bQ2p4+v2gGO2jA80jQoJO2m1xjU9rYWQW3+ErWy4I5YIuQHTBfb6hSdAHbaRfqPDgbv9J2rjEg==} + '@aws-sdk/middleware-recursion-detection@3.957.0': + resolution: {integrity: sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.782.0': - resolution: {integrity: sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA==} + '@aws-sdk/middleware-sdk-s3@3.936.0': + resolution: {integrity: sha512-UQs/pVq4cOygsnKON0pOdSKIWkfgY0dzq4h+fR+xHi/Ng3XzxPJhWeAE6tDsKrcyQc1X8UdSbS70XkfGYr5hng==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.775.0': - resolution: {integrity: sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==} + '@aws-sdk/middleware-sdk-s3@3.957.0': + resolution: {integrity: sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.775.0': - resolution: {integrity: sha512-cnGk8GDfTMJ8p7+qSk92QlIk2bmTmFJqhYxcXZ9PysjZtx0xmfCMxnG3Hjy1oU2mt5boPCVSOptqtWixayM17g==} + '@aws-sdk/middleware-ssec@3.957.0': + resolution: {integrity: sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.782.0': - resolution: {integrity: sha512-4tPuk/3+THPrzKaXW4jE2R67UyGwHLFizZ47pcjJWbhb78IIJAy94vbeqEQ+veS84KF5TXcU7g5jGTXC0D70Wg==} + '@aws-sdk/middleware-user-agent@3.936.0': + resolution: {integrity: sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.775.0': - resolution: {integrity: sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==} + '@aws-sdk/middleware-user-agent@3.957.0': + resolution: {integrity: sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-arn-parser@3.723.0': - resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==} + '@aws-sdk/nested-clients@3.936.0': + resolution: {integrity: sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.782.0': - resolution: {integrity: sha512-/RJOAO7o7HI6lEa4ASbFFLHGU9iPK876BhsVfnl54MvApPVYWQ9sHO0anOUim2S5lQTwd/6ghuH3rFYSq/+rdw==} + '@aws-sdk/nested-clients@3.958.0': + resolution: {integrity: sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.208.0': - resolution: {integrity: sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==} - engines: {node: '>=14.0.0'} + '@aws-sdk/region-config-resolver@3.936.0': + resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.775.0': - resolution: {integrity: sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==} + '@aws-sdk/region-config-resolver@3.957.0': + resolution: {integrity: sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-node@3.782.0': - resolution: {integrity: sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA==} + '@aws-sdk/signature-v4-multi-region@3.936.0': + resolution: {integrity: sha512-8qS0GFUqkmwO7JZ0P8tdluBmt1UTfYUah8qJXGzNh9n1Pcb0AIeT117cCSiCUtwk+gDbJvd4hhRIhJCNr5wgjg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.957.0': + resolution: {integrity: sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.936.0': + resolution: {integrity: sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.958.0': + resolution: {integrity: sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.936.0': + resolution: {integrity: sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.957.0': + resolution: {integrity: sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.957.0': + resolution: {integrity: sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.936.0': + resolution: {integrity: sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.957.0': + resolution: {integrity: sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-user-agent-browser@3.936.0': + resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} + + '@aws-sdk/util-user-agent-browser@3.957.0': + resolution: {integrity: sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==} + + '@aws-sdk/util-user-agent-node@3.936.0': + resolution: {integrity: sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -1705,108 +1841,83 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.775.0': - resolution: {integrity: sha512-b9NGO6FKJeLGYnV7Z1yvcP1TNU4dkD5jNsLWOF1/sygZoASaQhNOlaiJ/1OH331YQ1R1oWk38nBb0frsYkDsOQ==} + '@aws-sdk/util-user-agent-node@3.957.0': + resolution: {integrity: sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.930.0': + resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} engines: {node: '>=18.0.0'} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@aws-sdk/xml-builder@3.957.0': + resolution: {integrity: sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.2.2': + resolution: {integrity: sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==} + engines: {node: '>=18.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.7': - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.23.5': - resolution: {integrity: sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/core@7.24.7': - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.23.5': - resolution: {integrity: sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==} + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.22.15': - resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.24.7': - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-environment-visitor@7.24.7': - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-function-name@7.24.7': - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-hoist-variables@7.24.7': - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.23.3': - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.24.7': - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.22.5': - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.24.7': - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.7': - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.23.5': - resolution: {integrity: sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.24.7': - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.6': - resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1825,6 +1936,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -1835,8 +1958,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.23.3': - resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1871,40 +1994,38 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.23.3': - resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.23.4': - resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.0': - resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/template@7.22.15': - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.24.7': - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -1914,106 +2035,99 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@bundled-es-modules/cookie@2.0.1': - resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} - '@bundled-es-modules/statuses@1.0.1': - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - - '@bundled-es-modules/tough-cookie@0.1.6': - resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - - '@canvas/image-data@1.0.0': - resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} + '@canvas/image-data@1.1.0': + resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==} '@chainsafe/is-ip@2.1.0': resolution: {integrity: sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==} - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} + '@cropper/element-canvas@2.1.0': + resolution: {integrity: sha512-el+rfJpZxsD2q5XxDBA4fRczcrOqB65Lb7roqXOq8LKufwf4bPWA9C6DjNJJahh/TP94dsLIEy3tSkgRMDv3Aw==} - '@cropper/element-canvas@2.0.0': - resolution: {integrity: sha512-GPtGJgSm92crJhhhwUsaMw3rz2KfJWWSz7kRAlufFEV/EHTP5+6r6/Z1BCGRna830i+Avqbm435XLOtA7PVJwA==} + '@cropper/element-crosshair@2.1.0': + resolution: {integrity: sha512-0V589dAx8uZAfvJwdINLn76gfPQEafPH94ukjJ76uX0FCUovLaAVX+VRD/MDSYn0Mza/xejzmL9Dhd1DfemvmA==} - '@cropper/element-crosshair@2.0.0': - resolution: {integrity: sha512-KfPfyrdeFvUC31Ws7ATtcalWWSaMtrC6bMoCipZhqbUOE7wZoL4ecDSL6BUOZxPa74awZUqfzirCDjHvheBfyw==} + '@cropper/element-grid@2.1.0': + resolution: {integrity: sha512-dEnk0rO+vp553LMvsPYgfrqVFcYXeVFrgFeavBYYEhAXtO40p7kN4rmLYLMMjaN+T/Mx2BATv6kUQpALKy2HLw==} - '@cropper/element-grid@2.0.0': - resolution: {integrity: sha512-i78SQ0IJTLFveKX6P7svkfMYVdgHrQ8ZmmEw8keFy9n1ZVbK+SK0UHK5FNMRNI/gtVhKJOGEnK/zeyjUdj4Iyw==} + '@cropper/element-handle@2.1.0': + resolution: {integrity: sha512-8BklWA4C/2GGAULupIWleSnGutECvYt3vx9flodqDfZpDEozws4LgLqmmzVuQmVkRVUdLnXdtx28kjgWLtzkHg==} - '@cropper/element-handle@2.0.0': - resolution: {integrity: sha512-ZJvW+0MkK9E8xYymGdoruaQn2kwjSHFpNSWinjyq6csuVQiCPxlX5ovAEDldmZ9MWePPtWEi3vLKQOo2Yb0T8g==} + '@cropper/element-image@2.1.0': + resolution: {integrity: sha512-mXOV8ixJvG0XtTxLebYAKDjEkFbFOQnsF02hXPZk1yQSV0J+LLhN7a2NePrtKnoTsEV19fhhX3UorMoyGGxvzg==} - '@cropper/element-image@2.0.0': - resolution: {integrity: sha512-9BxiTS/aHRmrjopaFQb9mQQXmx4ruhYHGkDZMVz24AXpMFjUY6OpqrWse/WjzD9tfhMFvEdu17b3VAekcAgpeg==} + '@cropper/element-selection@2.1.0': + resolution: {integrity: sha512-mtFtBl6HIa/s9TWohXw+Z5eJoeYTqylrIcHvS7oVv0uM7IyeRwBW65Q7z+KtLfq/LW+2Sw/XDyvR+VN/DawBPw==} - '@cropper/element-selection@2.0.0': - resolution: {integrity: sha512-ensNnbIfJsJ8bhbJTH/RXtk2URFvTOO4TvfRk461n2FPEC588D7rwBmUJxQg74IiTi4y1JbCI+6j+4LyzYBLCQ==} + '@cropper/element-shade@2.1.0': + resolution: {integrity: sha512-zMdyqbb0lc0Vd1oj2Z1miIZvhyZG41OXMHvrNt0hNwblh0dVdrvtw48lnFDgRv+672vt2CNx7Q04GuvCQfPlgg==} - '@cropper/element-shade@2.0.0': - resolution: {integrity: sha512-jv/2bbNZnhU4W+T4G0c8ADocLIZvQFTXgCf2RFDNhI5UVxurzWBnDdb8Mx8LnVplnkTqO+xUmHZYve0CwgWo+Q==} + '@cropper/element-viewer@2.1.0': + resolution: {integrity: sha512-XnxlQuqHitd1FOFZ6E0yXAF5NYd/LyIvONLLHI9p1rJw747WYKUPxQaSYtFKF7IOizJu/8mMj++Zc1dZ5ZP3YQ==} - '@cropper/element-viewer@2.0.0': - resolution: {integrity: sha512-zY+3VRN5TvpM8twlphYtXw0tzJL2VgzeK7ufhL1BixVqOdRxwP13TprYIhqwGt9EW/SyJZUiaIu396T89kRX8A==} + '@cropper/element@2.1.0': + resolution: {integrity: sha512-2zELddqHQNmlvkPoiYzE5nxEjPE+C8nXoTPuvV3FvLp3YjBinc7qb73Icg9UXP0o9qC4+h9q96JgGo0AyMO/Ng==} - '@cropper/element@2.0.0': - resolution: {integrity: sha512-lsthn0nQq73GExUE7Mg/ss6Q3RXADGDv055hxoLFwvl/wGHgy6ZkYlfLZ/VmgBHC6jDK5IgPBFnqrPqlXWSGBA==} + '@cropper/elements@2.1.0': + resolution: {integrity: sha512-qvzlYDn3VQgPPpsCu6Gi1XUO0v3vpXQFSjjxcVijbXeNsl/eiKrN7H9/CEiRgi5vr8kXfd7ZvgYxBjUBbH+y+w==} - '@cropper/elements@2.0.0': - resolution: {integrity: sha512-PQkPo1nUjxLFUQuHYu+6atfHxpX9B41Xribao6wpvmvmNIFML6LQdNqqWYb6LyM7ujsu71CZdBiMT5oetjJVoQ==} + '@cropper/utils@2.1.0': + resolution: {integrity: sha512-wLtpZ4/UWgo+fGmG8NBWge8x5ehjfDe9ovleDfLy8kpwFaw43XXOEXQtRL1UNr0u4JZxaeO8FcXcolRWUUrlRQ==} - '@cropper/utils@2.0.0': - resolution: {integrity: sha512-cprLYr+7kK3faGgoOsTW9gIn5sefDr2KwOmgyjzIXk+8PLpW8FgFKEg5FoWfRD5zMAmkCBuX6rGKDK3VdUEGrg==} - - '@csstools/color-helpers@5.0.1': - resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} - '@csstools/css-calc@2.1.1': - resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.7': - resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-parser-algorithms@3.0.4': - resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-tokenizer@3.0.3': - resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + '@csstools/css-syntax-patches-for-csstree@1.0.22': + resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==} engines: {node: '>=18'} - '@cypress/request@3.0.7': - resolution: {integrity: sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==} - engines: {node: '>= 6'} + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} - '@cypress/request@3.0.8': - resolution: {integrity: sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==} + '@cypress/request@3.0.9': + resolution: {integrity: sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==} engines: {node: '>= 6'} '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - '@digitalbazaar/http-client@3.4.1': - resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==} - engines: {node: '>=14.0'} + '@digitalbazaar/http-client@4.2.0': + resolution: {integrity: sha512-OGju/GYp0V72qlZ/Pd4jGEwqBwT/Za/tw+Z3AC7lgMheGqsbhTZrtc5iLz9z59G/Q53QyE2fnjHV8N9wjBpiWA==} + engines: {node: '>=18.0'} - '@discordapp/twemoji@15.1.0': - resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==} + '@discordapp/twemoji@16.0.1': + resolution: {integrity: sha512-figLiBWzjS5cyrAjLaGjM8AAaowO3qvK8rg5bA2dElB4qsaPMvBVlFDMO2d3x+nC1igt7kgWH4dvNmvvUHUF8w==} - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} '@esbuild/aix-ppc64@0.19.11': resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} @@ -2021,14 +2135,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.0': - resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.25.2': - resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -2039,14 +2147,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.0': - resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.25.2': - resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -2057,14 +2159,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.0': - resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.25.2': - resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -2075,14 +2171,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.0': - resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.25.2': - resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -2093,14 +2183,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.0': - resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.25.2': - resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -2111,14 +2195,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.0': - resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.2': - resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -2129,14 +2207,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.0': - resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.25.2': - resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -2147,14 +2219,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.0': - resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.2': - resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -2165,14 +2231,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.0': - resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.25.2': - resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -2183,14 +2243,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.0': - resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.25.2': - resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -2201,14 +2255,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.0': - resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.25.2': - resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -2219,14 +2267,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.0': - resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.25.2': - resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -2237,14 +2279,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.0': - resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.25.2': - resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -2255,14 +2291,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.0': - resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.25.2': - resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -2273,14 +2303,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.0': - resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.2': - resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -2291,14 +2315,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.0': - resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.25.2': - resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -2309,26 +2327,14 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.0': - resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.2': - resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.0': - resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.25.2': - resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -2339,26 +2345,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.0': - resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.2': - resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.0': - resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.25.2': - resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -2369,17 +2363,17 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.0': - resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.2': - resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.19.11': resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} @@ -2387,14 +2381,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.0': - resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.25.2': - resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -2405,14 +2393,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.0': - resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.25.2': - resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -2423,14 +2405,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.0': - resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.25.2': - resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -2441,14 +2417,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.0': - resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.25.2': - resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -2459,134 +2429,164 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.1.1': - resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.4.0': + resolution: {integrity: sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-array@0.19.2': - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.1.0': - resolution: {integrity: sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.12.0': - resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.0': - resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.22.0': - resolution: {integrity: sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fastify/accept-negotiator@2.0.0': - resolution: {integrity: sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==} + '@fastify/accept-negotiator@2.0.1': + resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} - '@fastify/accepts@5.0.2': - resolution: {integrity: sha512-pX0OrioMz3C2cuYFOGRCNMdP3sR6daTFjeSNFrWlZVutawpPIGI5opK5h4Qa6x6C9oavdDkAjA16orneE2jAFQ==} + '@fastify/accepts@5.0.4': + resolution: {integrity: sha512-XKtvD77ZLQ/4G5r1WhPua5+rTctt16DF4XUMBQuP8KM/Ic431GhfqjJoYvwS4aDaUhoRTiU9DGFaMZ3TRM6ctg==} - '@fastify/ajv-compiler@4.0.0': - resolution: {integrity: sha512-dt0jyLAlay14LpIn4Fg1SY7V5NJ9KH0YFDpYVQY5cgIVBvdI8908AMx5zQ0bBYPGT6Wh+bM3f2caMmOXLP3QsQ==} + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} - '@fastify/busboy@2.1.0': - resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} - engines: {node: '>=14'} + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} - '@fastify/busboy@3.0.0': - resolution: {integrity: sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==} + '@fastify/cors@11.2.0': + resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==} - '@fastify/cookie@11.0.2': - resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} + '@fastify/deepmerge@3.1.0': + resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==} - '@fastify/cors@10.1.0': - resolution: {integrity: sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ==} - - '@fastify/deepmerge@2.0.0': - resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==} - - '@fastify/error@4.0.0': - resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==} + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} '@fastify/express@4.0.2': resolution: {integrity: sha512-lzu9MLdjlsK4Q2RiqEAwTgwQPrWQVP0kmbgAi/w9rIUqtnacjKvj3EHVTR6PIvXDs6Ut1jnTHiGbuNxHTsZwHQ==} - '@fastify/fast-json-stringify-compiler@5.0.0': - resolution: {integrity: sha512-tywfuZfXsyxLC5kEqrMubbFa9vpAxNtuPE7j9w5si1r+6p5b981pDfZ5Y8HBqmjDQl+PABT7cV5jZgXI2j+I5g==} + '@fastify/fast-json-stringify-compiler@5.0.3': + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} - '@fastify/forwarded@3.0.0': - resolution: {integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==} + '@fastify/forwarded@3.0.1': + resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} - '@fastify/http-proxy@10.0.2': - resolution: {integrity: sha512-3IlTjOu9xtX5UPd/ZxY3ebYb6caINuBlr7iyVxYmh3zZLV0K5ted8yfU9UCNXVNs33RwbXD2RhDucc3z5BNgEA==} + '@fastify/http-proxy@11.4.1': + resolution: {integrity: sha512-sMYEIB0c7qCYutpZyS12c8xnVgmEMSUUVU2XjcNq2JzHf6Hta1BWcpnG5FXxR3WEm48uZNCi0MxnIYtwjwd21Q==} - '@fastify/merge-json-schemas@0.1.1': - resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} - '@fastify/multipart@9.0.3': - resolution: {integrity: sha512-pJogxQCrT12/6I5Fh6jr3narwcymA0pv4B0jbC7c6Bl9wnrxomEUnV0d26w6gUls7gSXmhG8JGRMmHFIPsxt1g==} + '@fastify/multipart@9.3.0': + resolution: {integrity: sha512-NpeKipTOjjL1dA7SSlRMrOWWtrE8/0yKOmeudkdQoEaz4sVDJw5MVdZIahsWhvpc3YTN7f04f9ep/Y65RKoOWA==} - '@fastify/proxy-addr@5.0.0': - resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==} + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} - '@fastify/reply-from@11.0.0': - resolution: {integrity: sha512-dv3o8hyy4sxhg1RN9l6ueM+PMMaIPKLjtL2T99H5M7h1Xt8d1RX3r+xC+sL5AqJqLvReX4N+7mTq9QDeB8i6Lg==} + '@fastify/reply-from@12.5.0': + resolution: {integrity: sha512-m7mTGjgtnpnZBk4I8r6eFJY8WB4kyvXJo2nAf5PBm5f3mj3P7G6H2D7mhmF25os/n6EGMWVyw/bpTUehvy0i8g==} - '@fastify/send@3.3.1': - resolution: {integrity: sha512-6pofeVwaHN+E/MAofCwDqkWUliE3i++jlD0VH/LOfU8TJlCkMUSgKvA9bawDdVXxjve7XrdYMyDmkiYaoGWEtA==} + '@fastify/send@4.1.0': + resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} - '@fastify/static@8.1.1': - resolution: {integrity: sha512-TW9eyVHJLytZNpBlSIqd0bl1giJkEaRaPZG+5AT3L/OBKq9U8D7g/OYmc2NPQZnzPURGhMt3IAWuyVkvd2nOkQ==} + '@fastify/static@8.3.0': + resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==} - '@fastify/view@10.0.2': - resolution: {integrity: sha512-tGjXFyDUMj5a+E8BBrQ2wpsVnpOfMq3cqy4WD8pnjWPE/HGNItBASUPoPUcX/QjPhxfuZZTYv2XdCmKXdcMZPw==} + '@file-type/xml@0.4.4': + resolution: {integrity: sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==} '@github/webauthn-json@2.1.1': resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==} + deprecated: 'Deprecated: Modern browsers support built-in WebAuthn JSON methods. Please use native browser methods instead. For more information, visit https://github.com/github/webauthn-json' hasBin: true + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + '@hapi/boom@10.0.1': resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} '@hapi/bourne@3.0.0': resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} - '@hapi/hoek@11.0.4': - resolution: {integrity: sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ==} + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.4': + resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==} + engines: {node: '>=14.0.0'} + '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - '@hapi/wreck@18.0.1': - resolution: {integrity: sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg==} + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} - '@hexagon/base64@1.1.27': - resolution: {integrity: sha512-PdUmzpvcUM3Rh39kvz9RdbPVYhMjBjdV7Suw7ZduP7urRLsZR8l5tzgSWKm7TExwBYDFwTnYrZbnE0rQ3N5NLQ==} + '@hapi/wreck@18.1.0': + resolution: {integrity: sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==} + + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -2597,146 +2597,172 @@ packages: resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} engines: {node: '>=10.10.0'} - '@humanwhocodes/retry@0.3.0': - resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.2': - resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} - engines: {node: '>=18.18'} - - '@img/sharp-darwin-arm64@0.34.1': - resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.1': - resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.1.0': - resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.1.0': - resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.1.0': - resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-arm@1.1.0': - resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.1.0': - resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.1.0': - resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-x64@1.1.0': - resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': - resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] + libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.1.0': - resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] + libc: [musl] - '@img/sharp-linux-arm64@0.34.1': - resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] - '@img/sharp-linux-arm@0.34.1': - resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] - '@img/sharp-linux-s390x@0.34.1': - resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] - '@img/sharp-linux-x64@0.34.1': - resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.1': - resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.1': - resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] - '@img/sharp-wasm32@0.34.1': - resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.34.1': - resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.1': - resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@inquirer/confirm@5.0.2': - resolution: {integrity: sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/core@10.1.0': - resolution: {integrity: sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==} - engines: {node: '>=18'} - - '@inquirer/figures@1.0.8': - resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} - engines: {node: '>=18'} - - '@inquirer/type@3.0.1': - resolution: {integrity: sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==} + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@ioredis/commands@1.2.0': - resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@ioredis/commands@1.4.0': + resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -2767,9 +2793,9 @@ packages: node-notifier: optional: true - '@jest/create-cache-key-function@29.7.0': - resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/create-cache-key-function@30.2.0': + resolution: {integrity: sha512-44F4l4Enf+MirJN8X/NhdGkl71k5rBYiwdVlo4HxOwbu0sHV8QKrGEedb1VUU4K3W7fBKE0HGfbn7eZm0Ti3zg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/environment@29.7.0': resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} @@ -2791,6 +2817,10 @@ packages: resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/reporters@29.7.0': resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2804,6 +2834,10 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/source-map@29.6.3': resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2824,47 +2858,60 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0': - resolution: {integrity: sha512-qYDdL7fPwLRI+bJNurVcis+tNgJmvWjH4YTBGXTA8xMuxFrnAz6E5o35iyzyKbq5J5Lr8mJGfrR5GXl+WGwhgQ==} + '@jest/types@30.2.0': + resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.3': + resolution: {integrity: sha512-9TGZuAX+liGkNKkwuo3FYJu7gHWT0vkBcf7GkOe7s7fmC19XwH/4u5u7sDIFrMooe558ORcmuBvBz7Ur5PlbHw==} peerDependencies: typescript: '>= 4.3.x' - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: typescript: optional: true - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/resolve-uri@3.1.0': - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@kitajs/html@4.2.11': + resolution: {integrity: sha512-gOe+zzCZKN2fPT1FUK32mHsr21ILcAOUUux/yDqQthInW8egN8RuxVp+zP3KhwWETVACkurBiKV9RWuNw+ceiw==} + engines: {node: '>=12'} - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@kitajs/ts-html-plugin@4.1.3': + resolution: {integrity: sha512-NlYrID5yMxfRKiO1eiiSC4MWveKe0ffoCJOZm4idNOqwimmLXr0g1NmvCcquOU2XLRrgzynxZqw6rhwR5CY5Nw==} + hasBin: true + peerDependencies: + '@kitajs/html': ^4.2.10 + typescript: ^5.6.2 - '@kurkle/color@0.3.2': - resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} - '@levischuck/tiny-cbor@0.2.2': - resolution: {integrity: sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==} + '@levischuck/tiny-cbor@0.2.11': + resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} - '@lukeed/csprng@1.0.1': - resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==} + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} '@lukeed/ms@2.0.2': @@ -2875,30 +2922,30 @@ packages: resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==} hasBin: true - '@mcaptcha/core-glue@0.1.0-alpha-5': - resolution: {integrity: sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA==} + '@mcaptcha/core-glue@0.1.0-rc1': + resolution: {integrity: sha512-P4SgUioJDR38QpnP9sPY72NyaYex8MXD6RbzrfKra+ngamT26XjqVZEHBiZU2RT7u0SsWhuko4N1ntNOghsgpg==} - '@mcaptcha/vanilla-glue@0.1.0-alpha-3': - resolution: {integrity: sha512-GT6TJBgmViGXcXiT5VOr+h/6iOnThSlZuCoOWncubyTZU9R3cgU5vWPkF7G6Ob6ee2CBe3yqBxxk24CFVGTVXw==} + '@mcaptcha/vanilla-glue@0.1.0-rc2': + resolution: {integrity: sha512-LDjn9lrKioJ3zwaQOfql7PXsnxCAHg7b1rPw7G0OxpvVE7xLB/a40SHfIIiocce2VS9TPI4MbcKm5pcuy8fU5g==} - '@mdx-js/react@3.0.1': - resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==} + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} peerDependencies: '@types/react': '>=16' react: '>=16' - '@microsoft/api-extractor-model@7.30.5': - resolution: {integrity: sha512-0ic4rcbcDZHz833RaTZWTGu+NpNgrxVNjVaor0ZDUymfDFzjA/Uuk8hYziIUIOEOSTfmIQqyzVwlzxZxPe7tOA==} + '@microsoft/api-extractor-model@7.32.2': + resolution: {integrity: sha512-Ussc25rAalc+4JJs9HNQE7TuO9y6jpYQX9nWD1DhqUzYPBr3Lr7O9intf+ZY8kD5HnIqeIRJX7ccCT0QyBy2Ww==} - '@microsoft/api-extractor@7.52.2': - resolution: {integrity: sha512-RX37V5uhBBPUvrrcmIxuQ8TPsohvr6zxo7SsLPOzBYcH9nbjbvtdXrts4cxHCXGOin9JR5ar37qfxtCOuEBTHA==} + '@microsoft/api-extractor@7.55.2': + resolution: {integrity: sha512-1jlWO4qmgqYoVUcyh+oXYRztZde/pAi7cSVzBz/rc+S7CoVzDasy8QE13dx6sLG4VRo8SfkkLbFORR6tBw4uGQ==} hasBin: true - '@microsoft/tsdoc-config@0.17.1': - resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} + '@microsoft/tsdoc-config@0.18.0': + resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} - '@microsoft/tsdoc@0.15.1': - resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} '@misskey-dev/browser-image-resizer@2024.1.0': resolution: {integrity: sha512-4EnO0zLW5NDtng3Gaz5MuT761uiuoOuplwX18wBqgj8w56LTU5BjLn/vbHwDIIe0j2gwqDYhMb7bDjmr1/Fomg==} @@ -2911,127 +2958,251 @@ packages: eslint: '>= 3' eslint-plugin-import: '>= 2' - '@misskey-dev/eslint-plugin@2.1.0': - resolution: {integrity: sha512-f++Vv1r3BQyGqEE0SB5algUZwRoTMZIYfVtpcuQ2fLuYUm0cQ5BBTs/gwAHPajVB2YD8F33gzPIReTKtuJyCwQ==} + '@misskey-dev/eslint-plugin@2.2.0': + resolution: {integrity: sha512-NlDtaFRaO2r4KRnacTiYK2Ax/z95ZvhszQ3gfZ7VWWQzPvgoQHSEBT6q94i1/HPzWrcR+l+76c3Y3T1V/yVPeg==} peerDependencies: '@eslint/compat': '>= 1' - '@stylistic/eslint-plugin': '>= 2' + '@eslint/js': '>= 9' + '@stylistic/eslint-plugin': '>= 5' '@typescript-eslint/eslint-plugin': '>= 8' '@typescript-eslint/parser': '>= 8' eslint: '>= 9' eslint-plugin-import: '>= 2' - globals: '>= 15' + globals: '>= 16' - '@misskey-dev/sharp-read-bmp@1.3.0': - resolution: {integrity: sha512-18K95y0tXTtwl4BVfQb0JCr/9KHoHOfTKUUmZ7ibjzbS4bR/kGKoRkADsrdqBllF3nvu7PQN8zjUoM4SWoBLBg==} + '@misskey-dev/sharp-read-bmp@1.2.0': + resolution: {integrity: sha512-er4pRakXzHYfEgOFAFfQagqDouG+wLm+kwNq1I30oSdIHDa0wM3KjFpfIGQ25Fks4GcmOl1s7Zh6xoQu5dNjTw==} - '@misskey-dev/summaly@5.2.0': - resolution: {integrity: sha512-Djg6emAOdcY81a8TMTFi/qgIdTO96Z2qZfhHE+k+YRd8z9V/5MxauJr+U9z4kUVjSJJ1yzdOWqi26OsmVUaXZA==} + '@misskey-dev/summaly@5.2.5': + resolution: {integrity: sha512-AadzhQ+FAjKdVUzcVjHh+MoOBxQOwcYiFlfKR2Y2K0Yc0NBFUs3z6J0iG1Mk2KdPjClMdsapnkFcZc50sgkgAg==} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2': - resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2': - resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} cpu: [x64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2': - resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} cpu: [arm64] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2': - resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==} + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} cpu: [arm] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2': - resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} cpu: [x64] os: [linux] - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2': - resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} cpu: [x64] os: [win32] - '@mswjs/interceptors@0.37.5': - resolution: {integrity: sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==} + '@mswjs/interceptors@0.40.0': + resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} engines: {node: '>=18'} - '@napi-rs/canvas-android-arm64@0.1.69': - resolution: {integrity: sha512-4icWTByY8zPvM9SelfQKf3I6kwXw0aI5drBOVrwfER5kjwXJd78FPSDSZkxDHjvIo9Q86ljl18Yr963ehA4sHQ==} + '@napi-rs/canvas-android-arm64@0.1.87': + resolution: {integrity: sha512-uW7NxJXPvZft9fers4oBhdCsBRVe77DLQS3eXEOxndFzGKiwmjIbZpQqj4QPvrg3I0FM3UfHatz1+17P5SeCOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.69': - resolution: {integrity: sha512-HOanhhYlHdukA+unjelT4Dg3ta7e820x87/AG2dKUMsUzH19jaeZs9bcYjzEy2vYi/dFWKz7cSv2yaIOudB8Yg==} + '@napi-rs/canvas-darwin-arm64@0.1.87': + resolution: {integrity: sha512-S6YbpXwajDKLTsYftEqR+Ne1lHpeC78okI3IqctVdFexN31Taprn6mdV4CkPY/4S8eGNuReBHvXNyWbGqBZ1eQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.69': - resolution: {integrity: sha512-SIp7WfhxAPnSVK9bkFfJp+84rbATCIq9jMUzDwpCLhQ+v+OqtXe4pggX1oeV+62/HK6BT1t18qRmJfyqwJ9f3g==} + '@napi-rs/canvas-darwin-x64@0.1.87': + resolution: {integrity: sha512-OJLwP2WIUmRSqWTyV/NZ2TnvBzUsbNqQu6IL7oshwfxYg4BELPV279wrfQ/xZFqzr7wybfIzKaPF4du5ZdA2Cg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.69': - resolution: {integrity: sha512-Ls+KujCp6TGpkuMVFvrlx+CxtL+casdkrprFjqIuOAnB30Mct6bCEr+I83Tu29s3nNq4EzIGjdmA3fFAZG/Dtw==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.87': + resolution: {integrity: sha512-Io3tY6ogc+oyvIGK9rQlnfH4gKiS35P7W6s22x3WCrLFR0dXzZP2IBBoEFEHd6FY6FR1ky5u9cRmADaiLRdX3g==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.69': - resolution: {integrity: sha512-m8VcGmeSBNRbHZBd1srvdM1aq/ScS2y8KqGqmCCEgJlytYK4jdULzAo2K/BPKE1v3xvn8oUPZDLI/NBJbJkEoA==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.87': + resolution: {integrity: sha512-Zq7h/PQzs37gaSR/gNRZOAaCC1kGt6NmDjA1PcqpONITh/rAfAwAeP98emrbBJ4FDoPkYRkxmxHlmXNLlsQIBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] - '@napi-rs/canvas-linux-arm64-musl@0.1.69': - resolution: {integrity: sha512-a3xjNRIeK2m2ZORGv2moBvv3vbkaFZG1QKMeiEv/BKij+rkztuEhTJGMar+buICFgS0fLgphXXsKNkUSJb7eRQ==} + '@napi-rs/canvas-linux-arm64-musl@0.1.87': + resolution: {integrity: sha512-CUa5YJjpsFcUxJbtfoQ4bqO/Rq+JU/2RfTNFxx07q1AjuDjCM8+MOOLCvVOV1z3qhl6nKAtjJT0pA0J8EbnK8Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.69': - resolution: {integrity: sha512-pClUoJF5wdC9AvD0mc15G9JffL1Q85nuH1rLSQPRkGmGmQOtRjw5E9xNbanz7oFUiPbjH7xcAXUjVAcf7tdgPQ==} + '@napi-rs/canvas-linux-riscv64-gnu@0.1.87': + resolution: {integrity: sha512-5KM4dBFEzFMNkJV2rheIQWpd+mRZA7VNDnxTT7nsCEf6DUjUnf6Hssq9bAwjVYTe4jqraDHbWRbF4uXLBLRFJg==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] - '@napi-rs/canvas-linux-x64-gnu@0.1.69': - resolution: {integrity: sha512-96X3bFAmzemfw84Ts6Jg/omL86uuynvK06MWGR/mp3JYNumY9RXofA14eF/kJIYelbYFWXcwpbcBR71lJ6G/YQ==} + '@napi-rs/canvas-linux-x64-gnu@0.1.87': + resolution: {integrity: sha512-zSv+ozz9elT5YhocyogX5LwVYURChO4QGD6CQIW6OnuNA0UOMDD/b4wDzlJiMphISy3EVTntlKFhe4W3EuKcxw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] - '@napi-rs/canvas-linux-x64-musl@0.1.69': - resolution: {integrity: sha512-2QTsEFO72Kwkj53W9hc5y1FAUvdGx0V+pjJB+9oQF6Ys9+y989GyPIl5wZDzeh8nIJW6koZZ1eFa8pD+pA5BFQ==} + '@napi-rs/canvas-linux-x64-musl@0.1.87': + resolution: {integrity: sha512-jTNmicAZQ70X+cbjZz6G6w8lmORwxRBmj/U20ECNYvcWVLshgyCKWPFL2I0Z6pkJve0vZWls6oZ15iccm1sv8w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] - '@napi-rs/canvas-win32-x64-msvc@0.1.69': - resolution: {integrity: sha512-Q4YA8kVnKarApBVLu7F8icGlIfSll5Glswo5hY6gPS4Is2dCI8+ig9OeDM8RlwYevUIxKq8lZBypN8Q1iLAQ7w==} + '@napi-rs/canvas-win32-arm64-msvc@0.1.87': + resolution: {integrity: sha512-p6J7UNAxKHYc7AL0glEtYuW/E0OLLUNnLti8dA2OT51p08Il4T7yZCl+iNo6f73HntFP+dgOHh2cTXUhmk8GuA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@0.1.87': + resolution: {integrity: sha512-WrwfETMLBRFWkGU8fXU50gCpA2eIjR4NE9JyTKl86Kz5g6SDp0CcuqS2phYtB66TI2HDUhTPbNrk4V7Qf1FOLA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.69': - resolution: {integrity: sha512-ydvNeJMRm+l3T14yCoUKqjYQiEdXDq1isznI93LEBGYssXKfSaLNLHOkeM4z9Fnw9Pkt2EKOCAtW9cS4b00Zcg==} + '@napi-rs/canvas@0.1.87': + resolution: {integrity: sha512-Zb5tePmPMOYBcuNW3NQaVM1sIkvIfel39euiOab/XMjC5Oc/AnPJLa/BacJcToGyIvehecS6eqcsF7i0Wqe1Sw==} engines: {node: '>= 10'} - '@nestjs/common@11.0.16': - resolution: {integrity: sha512-agvuQ8su4aZ+PVxAmY89odG1eR97HEQvxPmTMdDqyvDWzNerl7WQhUEd+j4/UyNWcF1or1UVcrtPj52x+eUSsA==} + '@napi-rs/nice-android-arm-eabi@1.1.1': + resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.1.1': + resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.1.1': + resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.1.1': + resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.1.1': + resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-musl@1.1.1': + resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-openharmony-arm64@1.1.1': + resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.1.1': + resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} + engines: {node: '>= 10'} + + '@nestjs/common@11.1.10': + resolution: {integrity: sha512-NoBzJFtq1bzHGia5Q5NO1pJNpx530nupbEu/auCWOFCGL5y8Zo8kiG28EXTCDfIhQgregEtn1Cs6H8WSLUC8kg==} peerDependencies: - class-transformer: '*' - class-validator: '*' - file-type: ^20.4.1 + class-transformer: '>=0.4.1' + class-validator: '>=0.13.2' reflect-metadata: ^0.1.12 || ^0.2.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -3040,8 +3211,8 @@ packages: class-validator: optional: true - '@nestjs/core@11.0.15': - resolution: {integrity: sha512-MVVoTzG3vCeIjjmGmKWTZqwJYF3OAHXUFYKRV+bwRaFANcK+54TJpTSmf34eOPnDrlFnurTldSBZV2Cja+uSRw==} + '@nestjs/core@11.1.10': + resolution: {integrity: sha512-LYpaacSb8X9dcRpeZxA7Mvi5Aozv11s6028ZNoVKY2j/fyThLd+xrkksg3u+sw7F8mipFaxS/LuVpoHQ/MrACg==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -3058,14 +3229,14 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@10.4.15': - resolution: {integrity: sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==} + '@nestjs/platform-express@11.1.10': + resolution: {integrity: sha512-B2kvhfY+pE41Y6MXuJs80T7yfYjXzqHkWVyZJ5CAa3nFN3X2OIca6RH+b+7l3wZ+4x1tgsv48Q2P8ZfrDqJWYQ==} peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 - '@nestjs/testing@11.0.15': - resolution: {integrity: sha512-IMeDGWuzcmEVClOC+jYVJFtyjIG4clzllndhp6ECNiWHNdKR55PU6ugjKBB8kZ5JszME8OaIXUYFTdiR5dcXXA==} + '@nestjs/testing@11.1.10': + resolution: {integrity: sha512-MiH1Cjtx84ceO/aCwcbuweJXnxpPzD7Qo2Ofiz2CIBy+YhH4u+NeGpGiqfoeEBOEEULQs1IaW2IbiPua7ChoYg==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3077,8 +3248,8 @@ packages: '@nestjs/platform-express': optional: true - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': @@ -3093,13 +3264,13 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@npmcli/agent@2.2.0': - resolution: {integrity: sha512-2yThA1Es98orMkpSLVqlDZAMPK3jHJhifP2gnNUdk1754uZ8yI5c+ulCoVG+WlntQA6MzhrURMXjSd9Z7dJ2/Q==} - engines: {node: ^16.14.0 || >=18.0.0} + '@npmcli/agent@4.0.0': + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/fs@3.1.0': - resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} '@nuxt/opencollective@0.4.1': resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} @@ -3118,347 +3289,343 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@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'} + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.0': 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'} + '@opentelemetry/context-async-hooks@2.2.0': + resolution: {integrity: sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==} + 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'} + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + 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'} + '@opentelemetry/instrumentation-amqplib@0.55.0': + resolution: {integrity: sha512-5ULoU8p+tWcQw5PDYZn8rySptGSLZHNX/7srqo2TioPnAAcvTy6sQFQXsNPrAnyRRtYGMetXVyZUy5OaX1+IfA==} + 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'} + '@opentelemetry/instrumentation-connect@0.52.0': + resolution: {integrity: sha512-GXPxfNB5szMbV3I9b7kNWSmQBoBzw7MT0ui6iU/p+NIzVx3a06Ri2cdQO7tG9EKb4aKSLmfX9Cw5cKxXqX6Ohg==} + 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'} + '@opentelemetry/instrumentation-dataloader@0.26.0': + resolution: {integrity: sha512-P2BgnFfTOarZ5OKPmYfbXfDFjQ4P9WkQ1Jji7yH5/WwB6Wm/knynAoA1rxbjWcDlYupFkyT0M1j6XLzDzy0aCA==} + 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'} + '@opentelemetry/instrumentation-express@0.57.0': + resolution: {integrity: sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg==} + 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'} + '@opentelemetry/instrumentation-fs@0.28.0': + resolution: {integrity: sha512-FFvg8fq53RRXVBRHZViP+EMxMR03tqzEGpuq55lHNbVPyFklSVfQBN50syPhK5UYYwaStx0eyCtHtbRreusc5g==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.19.0': - resolution: {integrity: sha512-JGwmHhBkRT2G/BYNV1aGI+bBjJu4fJUD/5/Jat0EWZa2ftrLV3YE8z84Fiij/wK32oMZ88eS8DI4ecLGZhpqsQ==} - engines: {node: '>=14'} + '@opentelemetry/instrumentation-generic-pool@0.52.0': + resolution: {integrity: sha512-ISkNcv5CM2IwvsMVL31Tl61/p2Zm2I2NAsYq5SSBgOsOndT0TjnptjufYVScCnD5ZLD1tpl4T3GEYULLYOdIdQ==} + 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'} + '@opentelemetry/instrumentation-graphql@0.56.0': + resolution: {integrity: sha512-IPvNk8AFoVzTAM0Z399t34VDmGDgwT6rIqCUug8P9oAGerl2/PEIYMPOl/rerPGu+q8gSWdmbFSjgg7PDVRd3Q==} + 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'} + '@opentelemetry/instrumentation-hapi@0.55.0': + resolution: {integrity: sha512-prqAkRf9e4eEpy4G3UcR32prKE8NLNlA90TdEU1UsghOTg0jUvs40Jz8LQWFEs5NbLbXHYGzB4CYVkCI8eWEVQ==} + 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'} + '@opentelemetry/instrumentation-http@0.208.0': + resolution: {integrity: sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ==} + 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'} + '@opentelemetry/instrumentation-ioredis@0.56.0': + resolution: {integrity: sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.47.0': - resolution: {integrity: sha512-4HqP9IBC8e7pW9p90P3q4ox0XlbLGme65YTrA3UTLvqvo4Z6b0puqZQP203YFu8m9rE/luLfaG7/xrwwqMUpJw==} - engines: {node: '>=14'} + '@opentelemetry/instrumentation-kafkajs@0.18.0': + resolution: {integrity: sha512-KCL/1HnZN5zkUMgPyOxfGjLjbXjpd4odDToy+7c+UsthIzVLFf99LnfIBE8YSSrYE4+uS7OwJMhvhg3tWjqMBg==} + 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'} + '@opentelemetry/instrumentation-knex@0.53.0': + resolution: {integrity: sha512-xngn5cH2mVXFmiT1XfQ1aHqq1m4xb5wvU6j9lSgLlihJ1bXzsO543cpDwjrZm2nMrlpddBf55w8+bfS4qDh60g==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-knex@0.44.0': - resolution: {integrity: sha512-SlT0+bLA0Lg3VthGje+bSZatlGHw/vwgQywx0R/5u9QC59FddTQSPJeWNw29M6f8ScORMeUOOTwihlQAn4GkJQ==} - engines: {node: '>=14'} + '@opentelemetry/instrumentation-koa@0.57.0': + resolution: {integrity: sha512-3JS8PU/D5E3q295mwloU2v7c7/m+DyCqdu62BIzWt+3u9utjxC9QS7v6WmUNuoDN3RM+Q+D1Gpj13ERo+m7CGg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.53.0': + resolution: {integrity: sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw==} + 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'} + '@opentelemetry/instrumentation-mongodb@0.61.0': + resolution: {integrity: sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==} + 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'} + '@opentelemetry/instrumentation-mongoose@0.55.0': + resolution: {integrity: sha512-5afj0HfF6aM6Nlqgu6/PPHFk8QBfIe3+zF9FGpX76jWPS0/dujoEYn82/XcLSaW5LPUDW8sni+YeK0vTBNri+w==} + 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'} + '@opentelemetry/instrumentation-mysql2@0.55.0': + resolution: {integrity: sha512-0cs8whQG55aIi20gnK8B7cco6OK6N+enNhW0p5284MvqJ5EPi+I1YlWsWXgzv/V2HFirEejkvKiI4Iw21OqDWg==} + 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'} + '@opentelemetry/instrumentation-mysql@0.54.0': + resolution: {integrity: sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A==} + 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'} + '@opentelemetry/instrumentation-pg@0.61.0': + resolution: {integrity: sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw==} + 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'} + '@opentelemetry/instrumentation-redis@0.57.0': + resolution: {integrity: sha512-bCxTHQFXzrU3eU1LZnOZQ3s5LURxQPDlU3/upBzlWY77qOI1GZuGofazj3jtzjctMJeBEJhNwIFEgRPBX1kp/Q==} + 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'} + '@opentelemetry/instrumentation-tedious@0.27.0': + resolution: {integrity: sha512-jRtyUJNZppPBjPae4ZjIQ2eqJbcRaRfJkr0lQLHFmOU/no5A6e9s1OHLd5XZyZoBJ/ymngZitanyRRA5cniseA==} + engines: {node: ^18.19.0 || >=20.6.0} 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-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-tedious@0.18.0': - resolution: {integrity: sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-undici@0.10.0': - resolution: {integrity: sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==} - engines: {node: '>=14'} + '@opentelemetry/instrumentation-undici@0.19.0': + resolution: {integrity: sha512-Pst/RhR61A2OoZQZkn6OLpdVpXp6qn3Y92wXa6umfJe9rV640r4bc6SWvw4pPN6DiQqPu2c8gnSSZPDtC6JlpQ==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.7.0 - '@opentelemetry/instrumentation@0.53.0': - resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==} - engines: {node: '>=14'} + '@opentelemetry/instrumentation@0.208.0': + resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.57.1': - resolution: {integrity: sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==} - 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@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/instrumentation@0.57.2': - resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} - engines: {node: '>=14'} + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/redis-common@0.36.2': - resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} + '@opentelemetry/semantic-conventions@1.38.0': + resolution: {integrity: sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==} engines: {node: '>=14'} - '@opentelemetry/resources@1.30.1': - resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.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/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/sql-common@0.40.1': - resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} - engines: {node: '>=14'} + '@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 - '@parcel/watcher-android-arm64@2.5.0': - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.5.0': - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.5.0': - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.5.0': - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.5.0': - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] - '@parcel/watcher-linux-arm-musl@2.5.0': - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] - '@parcel/watcher-linux-arm64-glibc@2.5.0': - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] - '@parcel/watcher-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] - '@parcel/watcher-linux-x64-glibc@2.5.0': - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] - '@parcel/watcher-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] - '@parcel/watcher-win32-arm64@2.5.0': - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-ia32@2.5.0': - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] - '@parcel/watcher-win32-x64@2.5.0': - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.5.0': - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} - '@peculiar/asn1-android@2.3.10': - resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==} + '@peculiar/asn1-android@2.6.0': + resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==} - '@peculiar/asn1-ecc@2.3.8': - resolution: {integrity: sha512-Ah/Q15y3A/CtxbPibiLM/LKcMbnLTdUdLHUgdpB5f60sSvGkXzxJCu5ezGTFHogZXWNX3KSmYqilCrfdmBc6pQ==} + '@peculiar/asn1-cms@2.6.0': + resolution: {integrity: sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==} - '@peculiar/asn1-rsa@2.3.8': - resolution: {integrity: sha512-ES/RVEHu8VMYXgrg3gjb1m/XG0KJWnV4qyZZ7mAg7rrF3VTmRbLxO8mk+uy0Hme7geSMebp+Wvi2U6RLLEs12Q==} + '@peculiar/asn1-csr@2.6.0': + resolution: {integrity: sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==} - '@peculiar/asn1-schema@2.3.8': - resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} + '@peculiar/asn1-ecc@2.6.0': + resolution: {integrity: sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==} - '@peculiar/asn1-x509@2.3.8': - resolution: {integrity: sha512-voKxGfDU1c6r9mKiN5ZUsZWh3Dy1BABvTM3cimf0tztNwyMJPhiXY94eRTgsMQe6ViLfT6EoXxkWVzcm3mFAFw==} + '@peculiar/asn1-pfx@2.6.0': + resolution: {integrity: sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==} + + '@peculiar/asn1-pkcs8@2.6.0': + resolution: {integrity: sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==} + + '@peculiar/asn1-pkcs9@2.6.0': + resolution: {integrity: sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==} + + '@peculiar/asn1-rsa@2.6.0': + resolution: {integrity: sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==} + + '@peculiar/asn1-schema@2.6.0': + resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==} + + '@peculiar/asn1-x509-attr@2.6.0': + resolution: {integrity: sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==} + + '@peculiar/asn1-x509@2.6.0': + resolution: {integrity: sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==} + + '@peculiar/x509@1.14.2': + resolution: {integrity: sha512-r2w1Hg6pODDs0zfAKHkSS5HLkOLSeburtcgwvlLLWWCixw+MmW3U6kD5ddyvc2Y2YdbGuVwCF2S2ASoU1cFAag==} + engines: {node: '>=22.0.0'} '@peertube/http-signature@1.7.0': resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==} engines: {node: '>=0.10'} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@prisma/instrumentation@5.22.0': - resolution: {integrity: sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==} + '@prisma/instrumentation@6.19.0': + resolution: {integrity: sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg==} + peerDependencies: + '@opentelemetry/api': ^1.8 - '@readme/better-ajv-errors@2.2.2': - resolution: {integrity: sha512-YBWor3QZhavGHqTEHJfYfk2e0UczQaImc0MIIJ5JNJrns4HKm94pwUdjkPVZO2eZJBt/Rzs/D8ZrdvFFwRh0zQ==} + '@readme/better-ajv-errors@2.4.0': + resolution: {integrity: sha512-9WODaOAKSl/mU+MYNZ2aHCrkoRSvmQ+1YkLj589OEqqjOAhbn8j7Z+ilYoiTu/he6X63/clsxxAB4qny9/dDzg==} engines: {node: '>=18'} peerDependencies: ajv: 4.11.8 - 8 - '@readme/json-schema-ref-parser@1.2.0': - resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} - deprecated: This package is no longer maintained. Please use `@apidevtools/json-schema-ref-parser` instead. - - '@readme/openapi-parser@2.7.0': - resolution: {integrity: sha512-P8WSr8WTOxilnT89tcCRKWYsG/II4sAwt1a/DIWub8xTtkrG9cCBBy/IUcvc5X8oGWN82MwcTA3uEkDrXZd/7A==} - engines: {node: '>=18'} + '@readme/openapi-parser@5.4.0': + resolution: {integrity: sha512-B02OhOTyNE62CadM06jaeXdDe2ujXNfeZVp66jS5ZWDB0kQbBYN9RtyxlLBZxgIc7nnw+DmVOaCiu/hoO5xYUw==} + engines: {node: '>=20'} peerDependencies: openapi-types: '>=7' @@ -3466,6 +3633,19 @@ packages: resolution: {integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==} engines: {node: '>=18'} + '@redocly/ajv@8.17.1': + resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} + + '@redocly/config@0.22.2': + resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} + + '@redocly/openapi-core@1.34.5': + resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rollup/plugin-json@6.1.0': resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} @@ -3475,8 +3655,8 @@ packages: rollup: optional: true - '@rollup/plugin-replace@6.0.2': - resolution: {integrity: sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==} + '@rollup/plugin-replace@6.0.3': + resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -3484,8 +3664,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -3493,216 +3673,256 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.39.0': - resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.39.0': - resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.39.0': - resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.39.0': - resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.39.0': - resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.39.0': - resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': - resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.39.0': - resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.39.0': - resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.39.0': - resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': - resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': - resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.39.0': - resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.39.0': - resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.39.0': - resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.39.0': - resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.39.0': - resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-win32-arm64-msvc@4.39.0': - resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.39.0': - resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.39.0': - resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} cpu: [x64] os: [win32] '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rushstack/node-core-library@5.13.0': - resolution: {integrity: sha512-IGVhy+JgUacAdCGXKUrRhwHMTzqhWwZUI+qEPcdzsb80heOw0QPbhhoVsoiMF7Klp8eYsp7hzpScMXmOa3Uhfg==} + '@rushstack/node-core-library@5.19.1': + resolution: {integrity: sha512-ESpb2Tajlatgbmzzukg6zyAhH+sICqJR2CNXNhXcEbz6UGCQfrKCtkxOpJTftWc8RGouroHG0Nud1SJAszvpmA==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/rig-package@0.5.3': - resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} - - '@rushstack/terminal@0.15.2': - resolution: {integrity: sha512-7Hmc0ysK5077R/IkLS9hYu0QuNafm+TbZbtYVzCMbeOdMjaRboLKrhryjwZSRJGJzu+TV1ON7qZHeqf58XfLpA==} + '@rushstack/problem-matcher@0.1.1': + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/ts-command-line@4.23.7': - resolution: {integrity: sha512-Gr9cB7DGe6uz5vq2wdr89WbVDKz0UeuFEn5H2CfWDe7JvjFFaiV15gi6mqDBTbHhHCWS7w8mF1h3BnIfUndqdA==} + '@rushstack/rig-package@0.6.0': + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} + + '@rushstack/terminal@0.19.5': + resolution: {integrity: sha512-6k5tpdB88G0K7QrH/3yfKO84HK9ggftfUZ51p7fePyCE7+RLLHkWZbID9OFWbXuna+eeCFE7AkKnRMHMxNbz7Q==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.1.5': + resolution: {integrity: sha512-YmrFTFUdHXblYSa+Xc9OO9FsL/XFcckZy0ycQ6q7VSBsVs5P0uD9vcges5Q9vctGlVdu27w+Ct6IuJ458V0cTQ==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sentry-internal/browser-utils@9.12.0': - resolution: {integrity: sha512-GXuDEG2Ix8DmVtTkjsItWdusk2CvJ6EPWKYVqFKifxt+IAT3ZbhGZd99Rg3wdRmt9xhCNuS4QrDzDTPMPgfdCw==} + '@sentry-internal/browser-utils@10.32.1': + resolution: {integrity: sha512-sjLLep1es3rTkbtAdTtdpc/a6g7v7bK5YJiZJsUigoJ4NTiFeMI5uIDCxbH/tjJ1q23YE1LzVn7T96I+qBRjHA==} engines: {node: '>=18'} - '@sentry-internal/feedback@9.12.0': - resolution: {integrity: sha512-3+UxoT97QIXNSUQS4ATL1FFws0RkUb6PeaQN8CPndI6mFlqTW5tuVVLNg9Eo1seNg7R/dfk6WHCWrYN1NbFFKQ==} + '@sentry-internal/feedback@10.32.1': + resolution: {integrity: sha512-O24G8jxbfBY1RE/v2qFikPJISVMOrd/zk8FKyl+oUVYdOxU2Ucjk2cR3EQruBFlc7irnL6rT3GPfRZ/kBgLkmQ==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@9.12.0': - resolution: {integrity: sha512-p8LuKZgWT/CoQBbDOXkSGjWWnc8WsnAayWgna8M/ZFWNITCNEM2rCuqZOyWOElIlrni+M7qoEA3jS7MZe8Ejxw==} + '@sentry-internal/node-cpu-profiler@2.2.0': + resolution: {integrity: sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==} engines: {node: '>=18'} - '@sentry-internal/replay@9.12.0': - resolution: {integrity: sha512-njEQosFeO/UX+gG+DMRANkPUuz6OIJLb+A1GVylhq9adUgFQydQ9Ay3v7/x1gMhdfHVP6Jeb27qkti0BWYbzBQ==} + '@sentry-internal/replay-canvas@10.32.1': + resolution: {integrity: sha512-/XGTzWNWVc+B691fIVekV2KeoHFEDA5KftrLFAhEAW7uWOwk/xy3aQX4TYM0LcPm2PBKvoumlAD+Sd/aXk63oA==} engines: {node: '>=18'} - '@sentry/browser@9.12.0': - resolution: {integrity: sha512-4xQYoZqi+VVhNvlhWiwRd57+SMr3Og4sLjuayAA+zIp1Wx/bDcIld697cugLwml/BR+mVJI2eokkgh1CBl6zag==} + '@sentry-internal/replay@10.32.1': + resolution: {integrity: sha512-KKmLUgIaLRM0VjrMA1ByQTawZyRDYSkG2evvEOVpEtR9F0sumidAQdi7UY71QEKE1RYe/Jcp/3WoaqsMh8tbnQ==} engines: {node: '>=18'} - '@sentry/core@8.55.0': - resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==} - engines: {node: '>=14.18'} - - '@sentry/core@9.12.0': - resolution: {integrity: sha512-jOqQK/90uzHmsBvkPTj/DAEFvA5poX4ZRyC7LE1zjg4F5jdOp3+M4W3qCy0CkSTu88Zu5VWBoppCU2Bs34XEqg==} + '@sentry/browser@10.32.1': + resolution: {integrity: sha512-NPNCXTZ05ZGTFyJdKNqjykpFm+urem0ebosILQiw3C4BxNVNGH4vfYZexyl6prRhmg91oB6GjVNiVDuJiap1gg==} engines: {node: '>=18'} - '@sentry/node@8.55.0': - resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==} - engines: {node: '>=14.18'} + '@sentry/core@10.32.1': + resolution: {integrity: sha512-PH2ldpSJlhqsMj2vCTyU0BI2Fx1oIDhm7Izo5xFALvjVCS0gmlqHt1udu6YlKn8BtpGH6bGzssvv5APrk+OdPQ==} + engines: {node: '>=18'} - '@sentry/opentelemetry@8.55.0': - resolution: {integrity: sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ==} - engines: {node: '>=14.18'} + '@sentry/node-core@10.32.1': + resolution: {integrity: sha512-w56rxdBanBKc832zuwnE+zNzUQ19fPxfHEtOhK8JGPu3aSwQYcIxwz9z52lOx3HN7k/8Fj5694qlT3x/PokhRw==} + engines: {node: '>=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 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/resources': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/semantic-conventions': ^1.37.0 - '@sentry/profiling-node@8.55.0': - resolution: {integrity: sha512-rYrlxbMlfQLHhkBUEC7bviuja1rojCb4+TtXi4NGnB4PppZeveGeuVTdJDWt3Ed6IBd20EEYoXv4+0aETbEnpw==} - engines: {node: '>=14.18'} - hasBin: true + '@sentry/node@10.32.1': + resolution: {integrity: sha512-oxlybzt8QW0lx/QaEj1DcvZDRXkgouewFelu/10dyUwv5So3YvipfvWInda+yMLmn25OggbloDQ0gyScA2jU3g==} + engines: {node: '>=18'} - '@sentry/vue@9.12.0': - resolution: {integrity: sha512-9/k6Jo1/nCJzj5qUNhQzzMCfBGT613CTrI2AauECyf44kkSE1FllvbCwT/V4eUMd+IiEL9qzqMLGTVwYUynxHw==} + '@sentry/opentelemetry@10.32.1': + resolution: {integrity: sha512-YLssSz5Y+qPvufrh2cDaTXDoXU8aceOhB+YTjT8/DLF6SOj7Tzen52aAcjNaifawaxEsLCC8O+B+A2iA+BllvA==} engines: {node: '>=18'} peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/semantic-conventions': ^1.37.0 + + '@sentry/profiling-node@10.32.1': + resolution: {integrity: sha512-UDSZayQw4K5wv/XNHoB+i+KrnlCStLb0H2lsypF4dgQFCrHXmbwhMh9ieofVGk5bxdmXoL3lSE+3W9cJbpqy2A==} + engines: {node: '>=18'} + hasBin: true + + '@sentry/vue@10.32.1': + resolution: {integrity: sha512-3KVvjkBw18FgbYar87CevQNPRATtBrzi+xIRZf6uJG2Wnd9w+WH3+CQsjKwDvQiYyChiW4CYuFL2DuQ/VqOxfQ==} + engines: {node: '>=18'} + peerDependencies: + '@tanstack/vue-router': ^1.64.0 pinia: 2.x || 3.x vue: 2.x || 3.x peerDependenciesMeta: + '@tanstack/vue-router': + optional: true pinia: optional: true - '@shikijs/core@3.2.2': - resolution: {integrity: sha512-yvlSKVMLjddAGBa2Yu+vUZxuu3sClOWW1AG+UtJkvejYuGM5BVL35s6Ijiwb75O9QdEx6IkMxinHZSi8ZyrBaA==} + '@shikijs/core@3.20.0': + resolution: {integrity: sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==} - '@shikijs/engine-javascript@3.2.2': - resolution: {integrity: sha512-tlDKfhWpF4jKLUyVAnmL+ggIC+0VyteNsUpBzh1iwWLZu4i+PelIRr0TNur6pRRo5UZIv3ss/PLMuwahg9S2hg==} + '@shikijs/engine-javascript@3.20.0': + resolution: {integrity: sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==} - '@shikijs/engine-oniguruma@3.2.2': - resolution: {integrity: sha512-vyXRnWVCSvokwbaUD/8uPn6Gqsf5Hv7XwcW4AgiU4Z2qwy19sdr6VGzMdheKKN58tJOOe5MIKiNb901bgcUXYQ==} + '@shikijs/engine-oniguruma@3.20.0': + resolution: {integrity: sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==} - '@shikijs/langs@3.2.2': - resolution: {integrity: sha512-NY0Urg2dV9ETt3JIOWoMPuoDNwte3geLZ4M1nrPHbkDS8dWMpKcEwlqiEIGqtwZNmt5gKyWpR26ln2Bg2ecPgw==} + '@shikijs/langs@3.20.0': + resolution: {integrity: sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==} - '@shikijs/themes@3.2.2': - resolution: {integrity: sha512-Zuq4lgAxVKkb0FFdhHSdDkALuRpsj1so1JdihjKNQfgM78EHxV2JhO10qPsMrm01FkE3mDRTdF68wfmsqjt6HA==} + '@shikijs/themes@3.20.0': + resolution: {integrity: sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==} - '@shikijs/types@3.2.2': - resolution: {integrity: sha512-a5TiHk7EH5Lso8sHcLHbVNNhWKP0Wi3yVnXnu73g86n3WoDgEra7n3KszyeCGuyoagspQ2fzvy4cpSc8pKhb0A==} + '@shikijs/types@3.20.0': + resolution: {integrity: sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sideway/address@4.1.4': - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} - '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -3712,34 +3932,32 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@simplewebauthn/server@12.0.0': - resolution: {integrity: sha512-aJdTe9GikOk40U7Q5Mm/Sqkxcq4a2oPZAcLcnyqMyFqrUaOS6vdsZW8/H3Mnsw9umcr88pcgB7kozPPt+5wOBw==} + '@simplewebauthn/server@13.2.2': + resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==} engines: {node: '>=20.0.0'} '@simplewebauthn/types@12.0.0': resolution: {integrity: sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@sindresorhus/is@5.6.0': resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} - '@sindresorhus/is@7.0.1': - resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} + '@sindresorhus/is@7.1.1': + resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} engines: {node: '>=18'} '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@sinonjs/commons@2.0.0': - resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} - - '@sinonjs/commons@3.0.0': - resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} - '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -3749,479 +3967,474 @@ packages: '@sinonjs/fake-timers@11.2.2': resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} - '@sinonjs/fake-timers@11.3.1': - resolution: {integrity: sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==} - '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} - '@sinonjs/samsam@8.0.0': - resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + '@sinonjs/fake-timers@15.1.0': + resolution: {integrity: sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==} + + '@sinonjs/samsam@8.0.3': + resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==} '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} - '@smithy/abort-controller@2.2.0': - resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==} + '@smithy/abort-controller@4.2.7': + resolution: {integrity: sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.5': + resolution: {integrity: sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.20.0': + resolution: {integrity: sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.7': + resolution: {integrity: sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.7': + resolution: {integrity: sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.7': + resolution: {integrity: sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.7': + resolution: {integrity: sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.7': + resolution: {integrity: sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.7': + resolution: {integrity: sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.8': + resolution: {integrity: sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.8': + resolution: {integrity: sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.7': + resolution: {integrity: sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.7': + resolution: {integrity: sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.7': + resolution: {integrity: sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} - '@smithy/abort-controller@4.0.2': - resolution: {integrity: sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==} + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader-native@4.0.0': - resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} + '@smithy/md5-js@4.2.7': + resolution: {integrity: sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader@5.0.0': - resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} + '@smithy/middleware-content-length@4.2.7': + resolution: {integrity: sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.1.0': - resolution: {integrity: sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==} + '@smithy/middleware-endpoint@4.4.1': + resolution: {integrity: sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==} engines: {node: '>=18.0.0'} - '@smithy/core@3.2.0': - resolution: {integrity: sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==} + '@smithy/middleware-retry@4.4.17': + resolution: {integrity: sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.0.2': - resolution: {integrity: sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==} + '@smithy/middleware-serde@4.2.8': + resolution: {integrity: sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@4.0.2': - resolution: {integrity: sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==} + '@smithy/middleware-stack@4.2.7': + resolution: {integrity: sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-browser@4.0.2': - resolution: {integrity: sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==} + '@smithy/node-config-provider@4.3.7': + resolution: {integrity: sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-config-resolver@4.1.0': - resolution: {integrity: sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==} + '@smithy/node-http-handler@4.4.7': + resolution: {integrity: sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-node@4.0.2': - resolution: {integrity: sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==} + '@smithy/property-provider@4.2.7': + resolution: {integrity: sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-universal@4.0.2': - resolution: {integrity: sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==} + '@smithy/protocol-http@5.3.7': + resolution: {integrity: sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.0.2': - resolution: {integrity: sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==} + '@smithy/querystring-builder@4.2.7': + resolution: {integrity: sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==} engines: {node: '>=18.0.0'} - '@smithy/hash-blob-browser@4.0.2': - resolution: {integrity: sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g==} + '@smithy/querystring-parser@4.2.7': + resolution: {integrity: sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==} engines: {node: '>=18.0.0'} - '@smithy/hash-node@4.0.2': - resolution: {integrity: sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==} + '@smithy/service-error-classification@4.2.7': + resolution: {integrity: sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==} engines: {node: '>=18.0.0'} - '@smithy/hash-stream-node@4.0.2': - resolution: {integrity: sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg==} + '@smithy/shared-ini-file-loader@4.4.2': + resolution: {integrity: sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==} engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@4.0.2': - resolution: {integrity: sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==} + '@smithy/signature-v4@5.3.7': + resolution: {integrity: sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==} engines: {node: '>=18.0.0'} - '@smithy/is-array-buffer@2.0.0': - resolution: {integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==} + '@smithy/smithy-client@4.10.2': + resolution: {integrity: sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.11.0': + resolution: {integrity: sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.7': + resolution: {integrity: sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} - '@smithy/is-array-buffer@4.0.0': - resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==} + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@4.0.2': - resolution: {integrity: sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==} + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.0.2': - resolution: {integrity: sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==} + '@smithy/util-defaults-mode-browser@4.3.16': + resolution: {integrity: sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.1.0': - resolution: {integrity: sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==} + '@smithy/util-defaults-mode-node@4.2.19': + resolution: {integrity: sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.1.0': - resolution: {integrity: sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==} + '@smithy/util-endpoints@3.2.7': + resolution: {integrity: sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.0.3': - resolution: {integrity: sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==} + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.0.2': - resolution: {integrity: sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==} + '@smithy/util-middleware@4.2.7': + resolution: {integrity: sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.0.2': - resolution: {integrity: sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==} + '@smithy/util-retry@4.2.7': + resolution: {integrity: sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@2.5.0': - resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==} + '@smithy/util-stream@4.5.8': + resolution: {integrity: sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@smithy/node-http-handler@4.0.4': - resolution: {integrity: sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==} + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.0.2': - resolution: {integrity: sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==} + '@smithy/util-waiter@4.2.7': + resolution: {integrity: sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@3.3.0': - resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==} - engines: {node: '>=14.0.0'} - - '@smithy/protocol-http@5.1.0': - resolution: {integrity: sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-builder@2.2.0': - resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==} - engines: {node: '>=14.0.0'} - - '@smithy/querystring-builder@4.0.2': - resolution: {integrity: sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-parser@4.0.2': - resolution: {integrity: sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==} - engines: {node: '>=18.0.0'} - - '@smithy/service-error-classification@4.0.2': - resolution: {integrity: sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==} - engines: {node: '>=18.0.0'} - - '@smithy/shared-ini-file-loader@4.0.2': - resolution: {integrity: sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.0.2': - resolution: {integrity: sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.2.0': - resolution: {integrity: sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==} - engines: {node: '>=18.0.0'} - - '@smithy/types@2.12.0': - resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} - engines: {node: '>=14.0.0'} - - '@smithy/types@4.2.0': - resolution: {integrity: sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==} - engines: {node: '>=18.0.0'} - - '@smithy/url-parser@4.0.2': - resolution: {integrity: sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-base64@4.0.0': - resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-browser@4.0.0': - resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-node@4.0.0': - resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-buffer-from@2.0.0': - resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@4.0.0': - resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==} - engines: {node: '>=18.0.0'} - - '@smithy/util-config-provider@4.0.0': - resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.0.8': - resolution: {integrity: sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.0.8': - resolution: {integrity: sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-endpoints@3.0.2': - resolution: {integrity: sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-hex-encoding@4.0.0': - resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-middleware@4.0.2': - resolution: {integrity: sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-retry@4.0.2': - resolution: {integrity: sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.2.0': - resolution: {integrity: sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-uri-escape@2.2.0': - resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-uri-escape@4.0.0': - resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-utf8@2.0.0': - resolution: {integrity: sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@4.0.0': - resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==} - engines: {node: '>=18.0.0'} - - '@smithy/util-waiter@4.0.3': - resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==} + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - '@storybook/addon-actions@8.6.12': - resolution: {integrity: sha512-B5kfiRvi35oJ0NIo53CGH66H471A3XTzrfaa6SxXEJsgxxSeKScG5YeXcCvLiZfvANRQ7QDsmzPUgg0o3hdMXw==} - peerDependencies: - storybook: ^8.6.12 + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@storybook/addon-backgrounds@8.6.12': - resolution: {integrity: sha512-lmIAma9BiiCTbJ8YfdZkXjpnAIrOUcgboLkt1f6XJ78vNEMnLNzD9gnh7Tssz1qrqvm34v9daDjIb+ggdiKp3Q==} + '@storybook/addon-actions@8.6.15': + resolution: {integrity: sha512-zc600PBJqP9hCyRY5escKgKf6Zt9kdNZfm+Jwb46k5/NMSO4tNVeOPGBFxW9kSsIYk8j55sNske+Yh60G+8bcw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-controls@8.6.12': - resolution: {integrity: sha512-9VSRPJWQVb9wLp21uvpxDGNctYptyUX0gbvxIWOHMH3R2DslSoq41lsC/oQ4l4zSHVdL+nq8sCTkhBxIsjKqdQ==} + '@storybook/addon-backgrounds@8.6.15': + resolution: {integrity: sha512-W36uEzMWPO/K3+8vV1R/GozdaFrIix0qqmxX0qoAT6/o4+zqHiloZkTF+2iuUTx/VmuztLcAoSaPDh8UPy3Q+g==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-docs@8.6.12': - resolution: {integrity: sha512-kEezQjAf/p3SpDzLABgg4fbT48B6dkT2LiZCKTRmCrJVtuReaAr4R9MMM6Jsph6XjbIj/SvOWf3CMeOPXOs9sg==} + '@storybook/addon-controls@8.6.15': + resolution: {integrity: sha512-CgV8WqGxQrqSKs1a/Y1v4mrsBJXGFmO5u4kvdhPbftRVfln11W4Hvc1SFmgXwGvmcwekAKH79Uwwkjhj3l6gzA==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-essentials@8.6.12': - resolution: {integrity: sha512-Y/7e8KFlttaNfv7q2zoHMPdX6hPXHdsuQMAjYl5NG9HOAJREu4XBy4KZpbcozRe4ApZ78rYsN/MO1EuA+bNMIA==} + '@storybook/addon-docs@8.6.15': + resolution: {integrity: sha512-Nm5LlxwAmGQRkCUY36FhtCLz21C+5XlydF7/bkBOHsf08p2xR5MNLMSPrIhte/PY7ne9viNUCm1d3d3LiWnkKg==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-highlight@8.6.12': - resolution: {integrity: sha512-9FITVxdoycZ+eXuAZL9ElWyML/0fPPn9UgnnAkrU7zkMi+Segq/Tx7y+WWanC5zfWZrXAuG6WTOYEXeWQdm//w==} + '@storybook/addon-essentials@8.6.15': + resolution: {integrity: sha512-BIcE/7t5WXDXs4+zycm7MLNPHA2219ImkKO70IH7uxGM4cm7jDuJ5v0crkAvNeeRVsZixT2P2L9EfUfi1cFCQg==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-interactions@8.6.12': - resolution: {integrity: sha512-cTAJlTq6uVZBEbtwdXkXoPQ4jHOAGKQnYSezBT4pfNkdjn/FnEeaQhMBDzf14h2wr5OgBnJa6Lmd8LD9ficz4A==} + '@storybook/addon-highlight@8.6.15': + resolution: {integrity: sha512-lOu44QTVw5nR8kzag0ukxWnLq48oy2MqMUDuMVFQWPBKX8ayhmgl2OiEcvAOVNsieTHrr2W4CkP7FFvF4D0vlg==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-links@8.6.12': - resolution: {integrity: sha512-AfKujFHoAxhxq4yu+6NwylltS9lf5MPs1eLLXvOlwo3l7Y/c68OdxJ7j68vLQhs9H173WVYjKyjbjFxJWf/YYg==} + '@storybook/addon-interactions@8.6.15': + resolution: {integrity: sha512-9qgu7jbPjzFm44UF57D6puK+/86maE26gY+06Thz1NpTBCjVIl2fTZ/CA00iXb5+12f3JmSF0w3XEjsqcrzd3w==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + storybook: ^8.6.15 + + '@storybook/addon-links@10.1.10': + resolution: {integrity: sha512-SVKFDb14mne16QMGkmOEk+T4NLvCuFJJ1ecebQ01cPiG5gM72LhzYkAro717Aizd6owyMqcWs0Rsfwl09qi5zA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.10 peerDependenciesMeta: react: optional: true - '@storybook/addon-mdx-gfm@8.6.12': - resolution: {integrity: sha512-OKI5+O8xyK8axGPFwkl38NGJ6Rjf7kyhiBPxw5NuHOjOnU/FL4Pw3QmY47TT96TVws27vP3gF5+FX8lj3Dd3rQ==} + '@storybook/addon-mdx-gfm@8.6.15': + resolution: {integrity: sha512-IYAoEj0LyVCw7rA9QbHsgPVLBYKqgK1capLZRy6MCKSkVpDUWx7R5QKjLjbQE9SYK+71oGCv9cr/HmfFU1xrLw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-measure@8.6.12': - resolution: {integrity: sha512-tACmwqqOvutaQSduw8SMb62wICaT1rWaHtMN3vtWXuxgDPSdJQxLP+wdVyRYMAgpxhLyIO7YRf++Hfha9RHgFg==} + '@storybook/addon-measure@8.6.15': + resolution: {integrity: sha512-F78fJlmuXMulTphFp9Iqx7I1GsbmNLboChnW/VqR6nRZx5o9cdGjc8IaEyXVFXZ7k1pnSvdaP5ndFmzkcPxQdg==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-outline@8.6.12': - resolution: {integrity: sha512-1ylwm+n1s40S91No0v9T4tCjZORu3GbnjINlyjYTDLLhQHyBQd3nWR1Y1eewU4xH4cW9SnSLcMQFS/82xHqU6A==} + '@storybook/addon-outline@8.6.15': + resolution: {integrity: sha512-rpGRLajsjBdpbggPmdNZbftF68zQwsYLosu7YiUSBaR4dm+gQ+7m5nLLI/MjZDHbt2nJRW94yXpn7dUw2CDF6g==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-storysource@8.6.12': - resolution: {integrity: sha512-EAvf7DubbIw8OnTCp/blmgDaO4hzL8rROR+SpNMx6t3NwFgfJTP4VosiNOFIrtdGOaUeG0I815XSUphjNQ14lw==} + '@storybook/addon-storysource@8.6.15': + resolution: {integrity: sha512-eTWVLUJfwMpqigyNhju1lNRGB0zNx5RJJFQymNsecJRnGx/uxoeyXPyrzhvaxEAnH4UQE72xK+DsCANYa4yEaw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-toolbars@8.6.12': - resolution: {integrity: sha512-HEcSzo1DyFtIu5/ikVOmh5h85C1IvK9iFKSzBR6ice33zBOaehVJK+Z5f487MOXxPsZ63uvWUytwPyViGInj+g==} + '@storybook/addon-toolbars@8.6.15': + resolution: {integrity: sha512-NfHAbOOu5qI9SQq6jJr2VfinaZpHrmz3bavBeUppxCxM+zfPuNudK8MlMOOuyPBPAoUqcDSoKZgNfCkOBQcyGg==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/addon-viewport@8.6.12': - resolution: {integrity: sha512-EXK2LArAnABsPP0leJKy78L/lbMWow+EIJfytEP5fHaW4EhMR6h7Hzaqzre6U0IMMr/jVFa1ci+m0PJ0eQc2bw==} + '@storybook/addon-viewport@8.6.15': + resolution: {integrity: sha512-ylTK4sehAeVTwcYMZyisyP3xX+m43NjJrQHKc3DAII3Z3RFqTv9l6CUMogM2/8mysTzoo8xYVtQB6hX7zB8Dew==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/blocks@8.6.12': - resolution: {integrity: sha512-DohlTq6HM1jDbHYiXL4ZvZ00VkhpUp5uftzj/CZDLY1fYHRjqtaTwWm2/OpceivMA8zDitLcq5atEZN+f+siTg==} + '@storybook/blocks@8.6.15': + resolution: {integrity: sha512-nc5jQkvPo0EirteHsrmcx9on/0lGQ8F4lUNky7kN2I5WM8Frr3cPTeRoAvzjUkOwrqt/vm3g+T4zSbmDq/OEDA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^8.6.12 + storybook: ^8.6.15 peerDependenciesMeta: react: optional: true react-dom: optional: true - '@storybook/builder-vite@8.6.12': - resolution: {integrity: sha512-Gju21ud/3Qw4v2vLNaa5SuJECsI9ICNRr2G0UyCCzRvCHg8jpA9lDReu2NqhLDyFIuDG+ZYT38gcaHEUoNQ8KQ==} + '@storybook/builder-vite@10.1.10': + resolution: {integrity: sha512-6m6zOyDhHLynv3lvkH70s1YoIkIFPhbpGsBKvHchRLrZLe8hCPeafIFLfZRPoD4yIPwBS6rWbjMsSvBMFlR+ag==} peerDependencies: - storybook: ^8.6.12 - vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + storybook: ^10.1.10 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/components@8.6.12': - resolution: {integrity: sha512-FiaE8xvCdvKC2arYusgtlDNZ77b8ysr8njAYQZwwaIHjy27TbR2tEpLDCmUwSbANNmivtc/xGEiDDwcNppMWlQ==} + '@storybook/components@8.6.15': + resolution: {integrity: sha512-+9GVKXPEW8Kl9zvNSTm9+VrJtx/puMZiO7gxCML63nK4aTWJXHQr4t9YUoGammSBM3AV1JglsKm6dBgJEeCoiA==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/core-events@8.6.12': - resolution: {integrity: sha512-j2MUlSfYOhTsjlruRWTqSVwYreJGFIsWeqHFAhCdtmXe3qpFBM/LuxTKuaM1uWvs6vEAyGEzDw8+DXwuO6uISg==} + '@storybook/core-events@8.6.15': + resolution: {integrity: sha512-u45S9Ls3DrnWZDdEqc4aoRP/GE7KY+KUJnpoCzvTmcwmEHZQrf9p9Hygbjr+fq8Yi+K6swlaprv17re6wdBjHg==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/core@8.6.12': - resolution: {integrity: sha512-t+ZuDzAlsXKa6tLxNZT81gEAt4GNwsKP/Id2wluhmUWD/lwYW0uum1JiPUuanw8xD6TdakCW/7ULZc7aQUBLCQ==} + '@storybook/csf-plugin@10.1.10': + resolution: {integrity: sha512-2dri4TRU8uuj/skmx/ZBw+GnnXf8EZHiMDMeijVRdBQtYFWPeoYzNIrGRpNfbuGpnDP0dcxrqti/TsedoxwFkA==} peerDependencies: - prettier: ^2 || ^3 + esbuild: '*' + rollup: '*' + storybook: ^10.1.10 + vite: '*' + webpack: '*' peerDependenciesMeta: - prettier: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: optional: true - '@storybook/csf-plugin@8.6.12': - resolution: {integrity: sha512-6s8CnP1aoKPb3XtC0jRLUp8M5vTA8RhGAwQDKUsFpCC7g89JR9CaKs9FY2ZSzsNbjR15uASi7b3K8BzeYumYQg==} + '@storybook/csf-plugin@8.6.15': + resolution: {integrity: sha512-ZLz/mtOoE1Jj2lE4pK3U7MmYrv5+lot3mGtwxGb832tcABMc97j9O+reCVxZYc7DeFbBuuEdMT9rBL/O3kXYmw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - '@storybook/icons@1.2.12': - resolution: {integrity: sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==} + '@storybook/icons@1.6.0': + resolution: {integrity: sha512-hcFZIjW8yQz8O8//2WTIXylm5Xsgc+lW9ISLgUk1xGmptIJQRdlhVIXCpSyLrQaaRiyhQRaVg7l3BD9S216BHw==} engines: {node: '>=14.0.0'} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - '@storybook/instrumenter@8.6.12': - resolution: {integrity: sha512-VK5fYAF8jMwWP/u3YsmSwKGh+FeSY8WZn78flzRUwirp2Eg1WWjsqPRubAk7yTpcqcC/km9YMF3KbqfzRv2s/A==} + '@storybook/icons@2.0.1': + resolution: {integrity: sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==} peerDependencies: - storybook: ^8.6.12 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/manager-api@8.6.12': - resolution: {integrity: sha512-O0SpISeJLNTQvhSBOsWzzkCgs8vCjOq1578rwqHlC6jWWm4QmtfdyXqnv7rR1Hk08kQ+Dzqh0uhwHx0nfwy4nQ==} + '@storybook/instrumenter@8.6.15': + resolution: {integrity: sha512-TvHR/+yyIAOp/1bLulFai2kkhIBtAlBw7J6Jd9DKyInoGhTWNE1G1Y61jD5GWXX29AlwaHfzGUaX5NL1K+FJpg==} + peerDependencies: + storybook: ^8.6.15 + + '@storybook/manager-api@8.6.15': + resolution: {integrity: sha512-ZOFtH821vFcwzECbFYFTKtSVO96Cvwwg45dMh3M/9bZIdN7klsloX7YNKw8OKvwE6XLFLsi2OvsNNcmTW6g88w==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/preview-api@8.6.12': - resolution: {integrity: sha512-84FE3Hrs0AYKHqpDZOwx1S/ffOfxBdL65lhCoeI8GoWwCkzwa9zEP3kvXBo/BnEDO7nAfxvMhjASTZXbKRJh5Q==} + '@storybook/preview-api@8.6.15': + resolution: {integrity: sha512-oqsp8f7QekB9RzpDqOXZQcPPRXXd/mTsnZSdAAQB/pBVqUpC9h/y5hgovbYnJ6DWXcpODbMwH+wbJHZu5lvm+w==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/react-dom-shim@8.6.12': - resolution: {integrity: sha512-51QvoimkBzYs8s3rCYnY5h0cFqLz/Mh0vRcughwYaXckWzDBV8l67WBO5Xf5nBsukCbWyqBVPpEQLww8s7mrLA==} + '@storybook/react-dom-shim@10.1.10': + resolution: {integrity: sha512-9pmUbEr1MeMHg9TG0c2jVUfHWr2AA86vqZGphY/nT6mbe/rGyWtBl5EnFLrz6WpI8mo3h+Kxs6p2oiuIYieRtw==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.10 - '@storybook/react-vite@8.6.12': - resolution: {integrity: sha512-UA2Kule99oyFgHdhcuhrRwCKyWu/yMbqbl9U7NwowFHNwWWFjVMMir/AmfShb/H1C1DQ3LqOad6/QwJyPLjP8g==} - engines: {node: '>=18.0.0'} + '@storybook/react-dom-shim@8.6.15': + resolution: {integrity: sha512-m2trBmmd4iom1qwrp1F109zjRDc0cPaHYhDQxZR4Qqdz8pYevYJTlipDbH/K4NVB6Rn687RT29OoOPfJh6vkFA==} peerDependencies: - '@storybook/test': 8.6.12 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - vite: ^4.0.0 || ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - '@storybook/test': - optional: true + storybook: ^8.6.15 - '@storybook/react@8.6.12': - resolution: {integrity: sha512-NzxlHLA5DkDgZM/dMwTYinuzRs6rsUPmlqP+NIv6YaciQ4NGnTYyOC7R/SqI6HHFm8ZZ5eMYvpfiFmhZ9rU+rQ==} - engines: {node: '>=18.0.0'} + '@storybook/react-vite@10.1.10': + resolution: {integrity: sha512-6kE4/88YuwO07P0DR6caKNDNvCB/VnpimPmj4Jv6qmqrBgnoOOiXHIKyHJD+EjNyrbbwv4ygG01RVEajpjQaDA==} peerDependencies: - '@storybook/test': 8.6.12 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - typescript: '>= 4.2.x' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.10 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@storybook/react@10.1.10': + resolution: {integrity: sha512-9Rpr8/wX0p5/EaulrxpqrjKjhGaA/Ab9HgxzTqs2Shz0gvMAQHoiRnTEp7RCCkP49ruFYnIp0yGRSovu03LakQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.10 + typescript: '>= 4.9.x' peerDependenciesMeta: - '@storybook/test': - optional: true typescript: optional: true - '@storybook/source-loader@8.6.12': - resolution: {integrity: sha512-Yfq54Vh1RnsUXqda6yd79gUoqjOfvig9t6a2eZDkLSBlFiYIUqHYCfMBFXxQTJN2pn0BlZccZs5ho85q3ULWWQ==} + '@storybook/source-loader@8.6.15': + resolution: {integrity: sha512-CdK0zmbiWpySLxq9qRJhDZRfz/sN6Cm1xV5ubkyvXZP16ggC9VNdQktupeMWtEHVwxJ4rGKK7OaMQcg8WDrYpw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/test@8.6.12': - resolution: {integrity: sha512-0BK1Eg+VD0lNMB1BtxqHE3tP9FdkUmohtvWG7cq6lWvMrbCmAmh3VWai3RMCCDOukPFpjabOr8BBRLVvhNpv2w==} + '@storybook/test@8.6.15': + resolution: {integrity: sha512-EwquDRUDVvWcZds3T2abmB5wSN/Vattal4YtZ6fpBlIUqONV4o/cOBX39cFfQSUCBrIXIjQ6RmapQCHK/PvBYw==} peerDependencies: - storybook: ^8.6.12 + storybook: ^8.6.15 - '@storybook/theming@8.6.12': - resolution: {integrity: sha512-6VjZg8HJ2Op7+KV7ihJpYrDnFtd9D1jrQnUS8LckcpuBXrIEbaut5+34ObY8ssQnSqkk2GwIZBBBQYQBCVvkOw==} + '@storybook/theming@8.6.15': + resolution: {integrity: sha512-dAbL0XOekyT6XsF49R6Etj3WxQ/LpdJDIswUUeHgVJ6/yd2opZOGbPxnwA3zlmAh1c0tvpPyhSDXxSG79u8e4Q==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/types@8.6.12': - resolution: {integrity: sha512-G/nR+js7KV1qKH3nAcOfwceERBic5e03dpkeA6PDmqBiQ8XeM9B6N4NTMhXi/2gM5ZAGJ+NxJMaW6zLnc32DjA==} + '@storybook/types@8.6.15': + resolution: {integrity: sha512-XZKcn8Y8P7pEMK8f+oBrilGCWGdzNTR4jc4EQjrp9ERbo4J9MsoVifnFW+bddRYdCxznCQ/5W7wDdoD6esyRkA==} peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/vue3-vite@8.6.12': - resolution: {integrity: sha512-ihYH2TiV14B8V1mrCVVrbjuf+F6+V/78oWofVkvnUQnpwH4CnAySGf6bz6c6/Y6qEr9r30ECUe6/sS0TMt1ZAQ==} - engines: {node: '>=18.0.0'} + '@storybook/vue3-vite@10.1.10': + resolution: {integrity: sha512-lH6Ae2BI++0QUASco6BXsECnf3V+5ZNtY2ij1/gY3RQFM9kGhHF0k9VWulK+1KmwnjRDWzh1PTZ6B4FC9DDq2Q==} peerDependencies: - storybook: ^8.6.12 - vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + storybook: ^10.1.10 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/vue3@8.6.12': - resolution: {integrity: sha512-mgGRMrFghDW5nHCDbdbhC4YUrOs7mCzwEuLZtdcvpB8TUPP62lTSnv3Gvcz8r12HjyIK6Jow9WgjTtdownGzkA==} - engines: {node: '>=18.0.0'} + '@storybook/vue3@10.1.10': + resolution: {integrity: sha512-NqMxLzW9VbuQOGbWIuwzi5yHcjP/iAUnmFtQ+Qm1vYGkxCdSmZAPiTo/nW3nK8KgPkSbSLumUbeCE7YyGBLiBA==} peerDependencies: - storybook: ^8.6.12 + storybook: ^10.1.10 vue: ^3.0.0 - '@stylistic/eslint-plugin@2.13.0': - resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==} + '@stylistic/eslint-plugin@5.5.0': + resolution: {integrity: sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=8.40.0' + eslint: '>=9.0.0' - '@swc/cli@0.6.0': - resolution: {integrity: sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==} + '@swc/cli@0.7.9': + resolution: {integrity: sha512-AFQu3ZZ9IcdClTknxbug08S9ed/q8F3aYkO5NoZ+6IjQ5UEo1s2HN1GRKNvUslYx2EoVYxd+6xGcp6C7wwtxyQ==} engines: {node: '>= 16.14.0'} hasBin: true peerDependencies: '@swc/core': ^1.2.66 - chokidar: 4.0.3 + chokidar: 5.0.0 peerDependenciesMeta: chokidar: optional: true @@ -4232,14 +4445,14 @@ packages: cpu: [arm64] os: [android] - '@swc/core-darwin-arm64@1.11.18': - resolution: {integrity: sha512-K6AntdUlNMQg8aChqjeXwnVhK6d4WRZ9TgtLSTmdU0Ugll4an7QK49s9NrT7XQU91cEsVvzdr++p1bNImx0hJg==} + '@swc/core-darwin-arm64@1.15.7': + resolution: {integrity: sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.11.18': - resolution: {integrity: sha512-RCRvC6Q9M5BArTvj/IzUAAYGrgxYFbTTnAtf6UX7JFq2DAn+hEwYUjmC1m0gFso9HqFU0m5QZUGfZvVmACGWUw==} + '@swc/core-darwin-x64@1.15.7': + resolution: {integrity: sha512-ZAFuvtSYZTuXPcrhanaD5eyp27H8LlDzx2NAeVyH0FchYcuXf0h5/k3GL9ZU6Jw9eQ63R1E8KBgpXEJlgRwZUQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] @@ -4250,59 +4463,63 @@ packages: cpu: [x64] os: [freebsd] - '@swc/core-linux-arm-gnueabihf@1.11.18': - resolution: {integrity: sha512-wteAKf8YKb3jOnZFm3EzuIMzzCVXMuQOLHsz1IgEOc44/gdgNXKxaYTWAowZuej7t68tf/w0cRNMc7Le414v/g==} + '@swc/core-linux-arm-gnueabihf@1.15.7': + resolution: {integrity: sha512-K3HTYocpqnOw8KcD8SBFxiDHjIma7G/X+bLdfWqf+qzETNBrzOub/IEkq9UaeupaJiZJkPptr/2EhEXXWryS/A==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.11.18': - resolution: {integrity: sha512-hY6jJYZ6PKHSBo5OATswfyKsUgsWu9+4nDcN8liYIRRgz3E0G9wk0VUTP4cFPivBFeHWTTAGz687/Nf2aQEIpw==} + '@swc/core-linux-arm64-gnu@1.15.7': + resolution: {integrity: sha512-HCnVIlsLnCtQ3uXcXgWrvQ6SAraskLA9QJo9ykTnqTH6TvUYqEta+TdTdGjzngD6TOE7XjlAiUs/RBtU8Z0t+Q==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] - '@swc/core-linux-arm64-musl@1.11.18': - resolution: {integrity: sha512-slu0mlP2nucvQalttnapfpqpD/LlM9NHx9g3ofgsLzjObyMEBiX4ZysQ3y65U8Mjw71RNqtLd/ZmvxI6OmLdiQ==} + '@swc/core-linux-arm64-musl@1.15.7': + resolution: {integrity: sha512-/OOp9UZBg4v2q9+x/U21Jtld0Wb8ghzBScwhscI7YvoSh4E8RALaJ1msV8V8AKkBkZH7FUAFB7Vbv0oVzZsezA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] - '@swc/core-linux-x64-gnu@1.11.18': - resolution: {integrity: sha512-h9a/8PA25arMCQ9t8CE8rA1s0c77z4kCZZ7dUuUkD88yEXIrARMca1IKR7of+S3slfQrf1Zlq3Ac1Fb1HVJziQ==} + '@swc/core-linux-x64-gnu@1.15.7': + resolution: {integrity: sha512-VBbs4gtD4XQxrHuQ2/2+TDZpPQQgrOHYRnS6SyJW+dw0Nj/OomRqH+n5Z4e/TgKRRbieufipeIGvADYC/90PYQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] - '@swc/core-linux-x64-musl@1.11.18': - resolution: {integrity: sha512-0sMDJj5qUGK9QEw4lrxLxkTP/4AoKciqNzXvqbk+J9XuXN2aIv4BsR1Y7z3GwAeMFGsba2lbHLOtJlDsaqIsiA==} + '@swc/core-linux-x64-musl@1.15.7': + resolution: {integrity: sha512-kVuy2unodso6p0rMauS2zby8/bhzoGRYxBDyD6i2tls/fEYAE74oP0VPFzxIyHaIjK1SN6u5TgvV9MpyJ5xVug==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] - '@swc/core-win32-arm64-msvc@1.11.18': - resolution: {integrity: sha512-zGv9HnfgBcKyt54MJRWdwRNu9BuYkAFM7bx+tWtKhd37Ef7ZX20QLs9xXl5wWDXCbsOdRxXIZgXs6PEL+Pzmrw==} + '@swc/core-win32-arm64-msvc@1.15.7': + resolution: {integrity: sha512-uddYoo5Xmo1XKLhAnh4NBIyy5d0xk33x1sX3nIJboFySLNz878ksCFCZ3IBqrt1Za0gaoIWoOSSSk0eNhAc/sw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.11.18': - resolution: {integrity: sha512-uBKj0S1lYv/E2ZhxHZOxSiQwoegYmzbPRpjq6eHBZDv97mu7W3K27/lsnPbvAfQ6b6rnv8BI+EsmJ7VLQBAHBQ==} + '@swc/core-win32-ia32-msvc@1.15.7': + resolution: {integrity: sha512-rqq8JjNMLx3QNlh0aPTtN/4+BGLEHC94rj9mkH1stoNRf3ra6IksNHMHy+V1HUqElEgcZyx+0yeXx3eLOTcoFw==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.11.18': - resolution: {integrity: sha512-8USTRcdgeFMNBgvVXl8tz6n4+9s9m+zHsfDeBT4jPgwnq2bnLBlTUlwnPwzDxfg9nUJr6RFD4xeKfWyZZRosZg==} + '@swc/core-win32-x64-msvc@1.15.7': + resolution: {integrity: sha512-4BK06EGdPnuplgcNhmSbOIiLdRgHYX3v1nl4HXo5uo4GZMfllXaCyBUes+0ePRfwbn9OFgVhCWPcYYjMT6hycQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.11.18': - resolution: {integrity: sha512-ORZxyCKKiqYt2iHdh1C7pfVR1GBjkuFOdwqZggQzaq0vt22DpGca+2JsUtkUoWQmWcct04v5+ScwgvsHuMObxA==} + '@swc/core@1.15.7': + resolution: {integrity: sha512-kTGB8XI7P+pTKW83tnUEDVP4zduF951u3UAOn5eTi0vyW6MvL56A3+ggMdfuVFtDI0/DsbSzf5z34HVBbuScWw==} engines: {node: '>=10'} peerDependencies: - '@swc/helpers': '*' + '@swc/helpers': '>=0.5.17' peerDependenciesMeta: '@swc/helpers': optional: true @@ -4310,14 +4527,14 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/jest@0.2.37': - resolution: {integrity: sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==} + '@swc/jest@0.2.39': + resolution: {integrity: sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==} engines: {npm: '>= 7.0.0'} peerDependencies: '@swc/core': '*' - '@swc/types@0.1.21': - resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} '@swc/wasm@1.2.130': resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==} @@ -4325,15 +4542,18 @@ packages: '@syuilo/aiscript@0.19.0': resolution: {integrity: sha512-ZWG4s1m6RrFjE7NeIMaxFz769YO1jW5ReTrOROrEO4IHheOrjxxJ/Ffe2TUNqX9/XxDloMwfWplKhfSzx8LGMA==} + '@syuilo/aiscript@1.2.1': + resolution: {integrity: sha512-jTW4dFGBJVNfq3kn0+tbtQqcr71sUILZpAs8tV2ZC/zO4y7LsG8hSbIQ9QfRWWWdnNjr5R6EoEoOsC1hGqxu0A==} + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tabler/icons-webfont@3.31.0': - resolution: {integrity: sha512-0a3Uhj9nKU5kYz6/MUIGuX7kRbbrnOvVL3LGnbIcW/fmEQMgVRN0lkXdeIVkIL6/JKDzI2zSm3X5I+hLIpzLog==} + '@tabler/icons-webfont@3.35.0': + resolution: {integrity: sha512-PRnv+lgj2Va6S1nTVcZoYDWZPXUaVNromQ9uxn0B1CbBOjqKXAzC/8yLc8XOXD2fH2DWT2XNw1XaNtlNpc0/Tw==} - '@tabler/icons@3.31.0': - resolution: {integrity: sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==} + '@tabler/icons@3.35.0': + resolution: {integrity: sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ==} '@tensorflow/tfjs-backend-cpu@4.22.0': resolution: {integrity: sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==} @@ -4387,12 +4607,22 @@ packages: resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/user-event@14.5.2': resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@testing-library/vue@8.1.0': resolution: {integrity: sha512-ls4RiHO1ta4mxqqajWRh8158uFObVrrtAPoxk7cIp4HrnQUj/ScKzqz53HxYpG3X6Zb7H2v+0eTGLSoy8HQ2nA==} engines: {node: '>=14'} @@ -4403,85 +4633,87 @@ packages: '@vue/compiler-sfc': optional: true + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} - '@tsd/typescript@5.4.5': - resolution: {integrity: sha512-saiCxzHRhUrRxQV2JhH580aQUZiKQUXI38FcAcikcfOomAil4G4lxT0RfrrKywoAYP/rqAdYXYmNRLppcd+hQQ==} + '@tsd/typescript@5.9.3': + resolution: {integrity: sha512-JSSdNiS0wgd8GHhBwnMAI18Y8XPhLVN+dNelPfZCXFhy9Lb3NbnFyp9JKxxr54jSUkEJPk3cidvCoHducSaRMQ==} engines: {node: '>=14.17'} - '@twemoji/parser@15.0.0': - resolution: {integrity: sha512-lh9515BNsvKSNvyUqbj5yFu83iIDQ77SwVcsN/SnEGawczhsKU6qWuogewN1GweTi5Imo5ToQ9s+nNTf97IXvg==} - - '@twemoji/parser@15.1.0': - resolution: {integrity: sha512-3HTiSxPvkWUJ4kZeCvwyKlIwkpTUfBOk6igpBBRQni58ceQMv5YK4smkc8vX/eqOlMMNER/9qobv+Q6Q8LVrqA==} - - '@twemoji/parser@15.1.1': - resolution: {integrity: sha512-CChRzIu6ngkCJOmURBlYEdX5DZSu+bBTtqR60XjBkFrmvplKW7OQsea+i8XwF4bLVlUXBO7ZmHhRPDzfQyLwwg==} + '@twemoji/parser@16.0.0': + resolution: {integrity: sha512-jmuIjkp3OIaEemwMy3sArBwZSuZkRqmueGwRe2Zk4cFzbUJISFBJSZLDUUBNIgq3c+nY49ideYN2OiII6JUqwA==} '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} - '@types/archiver@6.0.3': - resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} + '@types/archiver@7.0.0': + resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==} '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} - '@types/aria-query@5.0.1': - resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - '@types/babel__core@7.20.0': - resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.6.4': - resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - '@types/babel__template@7.4.1': - resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.0': - resolution: {integrity: sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==} + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@types/bcryptjs@2.4.6': - resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - - '@types/braces@3.0.1': - resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==} + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} '@types/canvas-confetti@1.9.0': resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/color-convert@2.0.4': resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==} - '@types/color-name@1.1.1': - resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} + '@types/color-name@1.1.5': + resolution: {integrity: sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==} - '@types/connect@3.4.35': - resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/connect@3.4.36': - resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} + '@types/content-disposition@0.5.9': + resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} - '@types/content-disposition@0.5.8': - resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/disposable-email-domains@1.0.2': - resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/disposable-email-domains@1.0.6': + resolution: {integrity: sha512-+jHw0Q4ERuVYIChlUaoSm/VEuuNFeW7JgUU8Rwa9V1ym6q+gkGmBK5sGTDKqlfmsSdI5bFMHKlEatirPFvd8Xw==} '@types/dlv@1.1.5': resolution: {integrity: sha512-JHOWNfiWepAhfwlSw17kiWrWrk6od2dEQgHltJw9AS0JPFoLZJBge5+Dnil2NfdjAvJ/+vGSX60/BRW20PpUXw==} @@ -4489,23 +4721,29 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/dom-mediacapture-transform@0.1.11': + resolution: {integrity: sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==} + + '@types/dom-webcodecs@0.1.13': + resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==} + '@types/eslint@7.29.0': resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.17.33': - resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} + '@types/express-serve-static-core@5.1.0': + resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} - '@types/express@4.17.17': - resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + '@types/express@5.0.4': + resolution: {integrity: sha512-g64dbryHk7loCIrsa0R3shBnEu5p6LPJ09bu9NG58+jz+cRUjFrc3Bz0kNQ7j9bXeCsrRDvNET1G54P/GJkAyA==} - '@types/fluent-ffmpeg@2.1.27': - resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==} + '@types/fluent-ffmpeg@2.1.28': + resolution: {integrity: sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==} - '@types/graceful-fs@4.1.6': - resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} '@types/hammerjs@2.0.46': resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} @@ -4513,23 +4751,26 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/htmlescape@1.1.3': - resolution: {integrity: sha512-tuC81YJXGUe0q8WRtBNW+uyx79rkkzWK651ALIXXYq5/u/IxjX4iHneGF2uUqzsNp+F+9J2mFZOv9jiLTtIq0w==} - '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/http-link-header@1.0.7': resolution: {integrity: sha512-snm5oLckop0K3cTDAiBnZDy6ncx9DJ3mCRDvs42C884MbVYPP74Tiq2hFsSDRTyjK6RyDYDIulPiW23ge+g5Lw==} - '@types/istanbul-lib-coverage@2.0.4': - resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + '@types/insert-text-at-cursor@0.3.2': + resolution: {integrity: sha512-S80ZeMGJ1YMwF/CwvvGNCoWS0klVpJmjtyEL1kbYgYXRMPMEfNWXXnnnLQaim3FNp6tJzOhy/yeQXInfC6o1sA==} - '@types/istanbul-lib-report@3.0.0': - resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - '@types/istanbul-reports@3.0.1': - resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} '@types/jest@29.5.12': resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} @@ -4540,8 +4781,8 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - '@types/jsdom@21.1.7': - resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -4552,59 +4793,56 @@ packages: '@types/jsonld@1.5.15': resolution: {integrity: sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==} - '@types/jsrsasign@10.5.15': - resolution: {integrity: sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==} - '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} - '@types/matter-js@0.19.8': - resolution: {integrity: sha512-W2ZWG58Lijv/4v768NgpeyFqqiOyslmAU7qqM1Lhz4XBoUgGtZtPz4CjcOKYtqHIak14dvPldslQhltqLTWwsw==} + '@types/matter-js@0.20.2': + resolution: {integrity: sha512-3PPKy3QxvZ89h9+wdBV2488I1JLVs7DEpIkPvgO8JC1mUdiVSO37ZIvVctOTD7hIq8OAL2gJ3ugGSuUip6DhCw==} - '@types/mdast@4.0.3': - resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/mdx@2.0.3': - resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} - '@types/micromatch@4.0.9': - resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==} + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - '@types/mime-types@2.1.4': - resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + '@types/micromatch@4.0.10': + resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==} - '@types/mime@3.0.1': - resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + '@types/mime-types@3.0.1': + resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==} - '@types/minimist@1.2.2': - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/ms@0.7.34': - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/mysql@2.15.26': - resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} '@types/node@20.11.17': resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} - '@types/node@22.13.10': - resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + '@types/node@20.19.25': + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} - '@types/node@22.13.15': - resolution: {integrity: sha512-imAbQEEbVni6i6h6Bd5xkCRwLqFc8hihCsi2GbtDoAtUcAFQ6Zs4pFXTZUUbroTkXdImczWM9AI8eZUuybXE3w==} + '@types/node@24.10.4': + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} - '@types/node@22.14.0': - resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} + '@types/nodemailer@7.0.4': + resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} - '@types/nodemailer@6.4.17': - resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} - - '@types/normalize-package-data@2.4.1': - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} '@types/oauth2orize-pkce@0.1.2': resolution: {integrity: sha512-g5rDzqQTTUIJJpY7UWxb0EU1WyURIwOj3TndKC2krEEEmaKrnZXgoEBkR72QY2kp4cJ6N9cF2AqTPJ0Qyg+caA==} @@ -4612,68 +4850,53 @@ packages: '@types/oauth2orize@1.11.5': resolution: {integrity: sha512-C6hrRoh9hCnqis39OpeUZSwgw+TIzcV0CsxwJMGfQjTx4I1r+CLmuEPzoDJr5NRTfc7OMwHNLkQwrGFLKrJjMQ==} - '@types/oauth@0.9.6': - resolution: {integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==} - '@types/offscreencanvas@2019.3.0': resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} - '@types/offscreencanvas@2019.7.0': - resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==} + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} - '@types/pg@8.11.11': - resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} - '@types/pg@8.6.1': - resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - - '@types/pug@2.0.10': - resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + '@types/pg@8.16.0': + resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} '@types/punycode@2.1.4': resolution: {integrity: sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==} - '@types/qrcode@1.5.5': - resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - '@types/qs@6.9.7': - resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} '@types/random-seed@0.3.5': resolution: {integrity: sha512-CftxcDPAHgs0SLHU2dt+ZlDPJfGqLW3sZlC/ATr5vJDSe5tRLeOne7HMvCOJnFyF8e1U41wqzs3h6AMC613xtA==} - '@types/range-parser@1.2.4': - resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} '@types/ratelimiter@3.4.6': resolution: {integrity: sha512-Bv6WLSXPGLVsBjkizXtn+ef78R92e36/DFQo2wXPTHtp1cYXF6rCULMqf9WcZPAtyMZMvQAtIPeYMA1xAyxghw==} - '@types/react@18.0.28': - resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} - '@types/readdir-glob@1.1.1': - resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} - - '@types/redis-info@3.0.3': - resolution: {integrity: sha512-VIkNy6JbYI/RLdbPHdm9JQvv6RVld2uE2/6Hdid38Qdq+zvDli2FTpImI8pC5zwp8xS8qVqfzlfyAub8xZEd5g==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} '@types/rename@1.0.7': resolution: {integrity: sha512-E9qapfghUGfBMi3jNhsmCKPIp3f2zvNKpaX1BDGLGJNjzpgsZ/RTx7NaNksFjGoJ+r9NvWF1NSM5vVecnNjVmw==} - '@types/resolve@1.20.3': - resolution: {integrity: sha512-NH5oErHOtHZYcjCtg69t26aXEk4BN2zLWqf7wnDZ+dpe0iR7Rds1SPGEItl3fca21oOe0n3OCnZ4W7jBxu7FOw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} - '@types/sanitize-html@2.15.0': - resolution: {integrity: sha512-71Z6PbYsVKfp4i6Jvr37s5ql6if1Q/iJQT80NbaSi7uGaG8CqBMXP0pk/EsURAOuGdk5IJCd/vnzKrR7S3Txsw==} - - '@types/scheduler@0.23.0': - resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} + '@types/sanitize-html@2.16.0': + resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} '@types/seedrandom@2.4.34': resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==} @@ -4681,42 +4904,54 @@ packages: '@types/seedrandom@3.0.8': resolution: {integrity: sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==} - '@types/semver@7.7.0': - resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/serve-static@1.15.1': - resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} '@types/serviceworker@0.0.74': resolution: {integrity: sha512-HNt7NJHrjGtCmI3h1+rsb1g/ZY0iy5KaeenfEV7zAWPSaCs49hEUvgH++V1BHNwlLfB3sbjPh3pSiNixfYjb1w==} - '@types/shimmer@1.2.0': - resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/simple-oauth2@5.0.8': + resolution: {integrity: sha512-TehQqoOGdy3/rmFsCEGgnt1f4JhUCA0joWemGGCTbVYvoZvfBjkRsBFYmz8k0V/sn2XQZHe33L4lWxqMhIO3tQ==} - '@types/simple-oauth2@5.0.7': - resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==} + '@types/sinon@17.0.4': + resolution: {integrity: sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==} - '@types/sinon@17.0.3': - resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==} + '@types/sinonjs__fake-timers@15.0.1': + resolution: {integrity: sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==} '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - '@types/sinonjs__fake-timers@8.1.5': - resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + '@types/sizzle@2.3.10': + resolution: {integrity: sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==} - '@types/sizzle@2.3.3': - resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/stack-utils@2.0.1': - resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/statuses@2.0.4': - resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@6.0.3': + resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/textarea-caret@3.0.4': + resolution: {integrity: sha512-epJGYB37/sNrTDbhfyRjHkXsQSAcO6zby0JBDS0QMt6HQ1f1W2E4YpSc7TQkNmWaWmYXv92zOIfN5PHA8CmThg==} + '@types/throttle-debounce@5.0.2': resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==} @@ -4726,14 +4961,11 @@ packages: '@types/tmp@0.2.6': resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} - '@types/tough-cookie@4.0.2': - resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} - '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - '@types/unist@3.0.2': - resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} @@ -4741,20 +4973,26 @@ packages: '@types/vary@1.1.3': resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==} + '@types/wawoff2@1.0.2': + resolution: {integrity: sha512-UVq4NxMuBywz5gklafg1HcENrK02trh/q+ifyvO6xktflGXRtf11GYsia3/l5nxg2edFQRlnUT6ivnk898WDiw==} + '@types/web-push@3.6.4': resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@types/yargs-parser@21.0.0': - resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.19': - resolution: {integrity: sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==} + '@types/yargs@17.0.34': + resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} - '@types/yauzl@2.10.0': - resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} '@typescript-eslint/eslint-plugin@6.18.1': resolution: {integrity: sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==} @@ -4767,29 +5005,13 @@ packages: typescript: optional: true - '@typescript-eslint/eslint-plugin@8.26.0': - resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} + '@typescript-eslint/eslint-plugin@8.50.1': + resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.50.1 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/eslint-plugin@8.29.0': - resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/eslint-plugin@8.29.1': - resolution: {integrity: sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/parser@6.18.1': resolution: {integrity: sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==} @@ -4801,42 +5023,32 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.26.0': - resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==} + '@typescript-eslint/parser@8.50.1': + resolution: {integrity: sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.29.0': - resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} + '@typescript-eslint/project-service@8.50.1': + resolution: {integrity: sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/parser@8.29.1': - resolution: {integrity: sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/scope-manager@6.18.1': resolution: {integrity: sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/scope-manager@8.26.0': - resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} + '@typescript-eslint/scope-manager@8.50.1': + resolution: {integrity: sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.29.0': - resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/scope-manager@8.29.1': - resolution: {integrity: sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==} + '@typescript-eslint/tsconfig-utils@8.50.1': + resolution: {integrity: sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/type-utils@6.18.1': resolution: {integrity: sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==} @@ -4848,41 +5060,23 @@ packages: typescript: optional: true - '@typescript-eslint/type-utils@8.26.0': - resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==} + '@typescript-eslint/type-utils@8.50.1': + resolution: {integrity: sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/type-utils@8.29.0': - resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/type-utils@8.29.1': - resolution: {integrity: sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/types@6.18.1': resolution: {integrity: sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@8.26.0': - resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} + '@typescript-eslint/types@8.50.1': + resolution: {integrity: sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.29.0': - resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/types@8.29.1': - resolution: {integrity: sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==} + '@typescript-eslint/types@8.51.0': + resolution: {integrity: sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@6.18.1': @@ -4894,23 +5088,11 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.26.0': - resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==} + '@typescript-eslint/typescript-estree@8.50.1': + resolution: {integrity: sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/typescript-estree@8.29.0': - resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/typescript-estree@8.29.1': - resolution: {integrity: sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/utils@6.18.1': resolution: {integrity: sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==} @@ -4918,58 +5100,75 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@8.26.0': - resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==} + '@typescript-eslint/utils@8.50.1': + resolution: {integrity: sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/utils@8.29.0': - resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/utils@8.29.1': - resolution: {integrity: sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/visitor-keys@6.18.1': resolution: {integrity: sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@8.26.0': - resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} + '@typescript-eslint/visitor-keys@8.50.1': + resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.29.0': - resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-30LtIeJ5AgVlteAFimEeFwVW8QcMHRBSk2nranz5sdc5bz9ed5WSN/CMdz/dSsWTbcWU3H2PPzbbEAnOSp3vNw==} + cpu: [arm64] + os: [darwin] - '@typescript-eslint/visitor-keys@8.29.1': - resolution: {integrity: sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-bHT90f10Y1eis9hmqQj5C/Fwj4cH1SYcihcl2zp++m1JJgpjdc+4Qqv2Mw8bKn3Jy6DNoRkodkuMAgaNOv1utQ==} + cpu: [x64] + os: [darwin] - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-D5XEpJoPPCaDeNIaYRbdjg5ytiLu9Sm6DxqzhYrj2Zn/Qedd9KQOgfqQXG41Z6RoGE+NjqkH1mr25JzsoedxIw==} + cpu: [arm64] + os: [linux] - '@vitejs/plugin-vue@5.2.3': - resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} - engines: {node: ^18.0.0 || >=20.0.0} + '@typescript/native-preview-linux-arm@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-ohwr5CLoLf/B/ie9fcIFbalee7LPO3IH+7UF1LDibFt5R0jT8mVaNQPFXDK19BvZor89HURelf4KNSENknLbHA==} + cpu: [arm] + os: [linux] + + '@typescript/native-preview-linux-x64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-wmjTdkHEmU1mSvYfEIUuwJs1m/SnIQUNPg/8/D3qFQHfiZmeUNjWHNEIUm9ihxC3buX+PsX9dMPq34kkoiPvbw==} + cpu: [x64] + os: [linux] + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-I7G32Lb5Py+88W5rZgaOElr0fColm9leBElQih+t+/3/oP1vBeAMiNI39LAgefVDNnhGU/7caiyQIiNK8qaVmw==} + cpu: [arm64] + os: [win32] + + '@typescript/native-preview-win32-x64@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-zdS1ul3dkDOFymUPOndYDPBjvXFipyNC225r/NfJIHOExvFWmipIt53PzyLcKAjs0ijoVCc5AzcIBHGwif2CqA==} + cpu: [x64] + os: [win32] + + '@typescript/native-preview@7.0.0-dev.20251226.1': + resolution: {integrity: sha512-sq37dwY02wBs4h1lzhYKktb62uHri43u3qVDUL6w+vK5IljJi8p8uzQPOECjXLFGvNxrzsSb6iZ5qB8t6qjfbQ==} + hasBin: true + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@6.0.3': + resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 vue: ^3.2.25 - '@vitest/coverage-v8@3.1.1': - resolution: {integrity: sha512-MgV6D2dhpD6Hp/uroUoAIvFqA8AuvXEFBC2eepG3WFc1pxTfdk1LEqqkWoWhjz+rytoqrnUUCdf6Lzco3iHkLQ==} + '@vitest/coverage-v8@4.0.16': + resolution: {integrity: sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==} peerDependencies: - '@vitest/browser': 3.1.1 - vitest: 3.1.1 + '@vitest/browser': 4.0.16 + vitest: 4.0.16 peerDependenciesMeta: '@vitest/browser': optional: true @@ -4977,14 +5176,28 @@ packages: '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - '@vitest/expect@3.1.1': - resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.1.1': - resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true @@ -4994,151 +5207,157 @@ packages: '@vitest/pretty-format@2.0.5': resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - '@vitest/pretty-format@2.1.1': - resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - '@vitest/pretty-format@3.1.1': - resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.1.1': - resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==} + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} - '@vitest/snapshot@3.1.1': - resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==} + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@vitest/spy@3.1.1': - resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - '@vitest/utils@2.1.1': - resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - '@vitest/utils@3.1.1': - resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@volar/language-core@2.2.0': - resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==} + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} - '@volar/language-core@2.4.11': - resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==} + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} - '@volar/source-map@2.2.0': - resolution: {integrity: sha512-HQlPRlHOVqCCHK8wI76ZldHkEwKsjp7E6idUc36Ekni+KJDNrqgSqPvyHQixybXPHNU7CI9Uxd9/IkxO7LuNBw==} + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} - '@volar/source-map@2.4.11': - resolution: {integrity: sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==} + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} - '@volar/typescript@2.2.0': - resolution: {integrity: sha512-wC6l4zLiiCLxF+FGaHCbWlQYf4vMsnRxYhcI6WgvaNppOD6r1g+Ef1RKRJUApALWU46Yy/JDU/TbdV6w/X6Liw==} + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} - '@volar/typescript@2.4.11': - resolution: {integrity: sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==} + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} - '@vue/compiler-core@3.5.13': - resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + '@volar/typescript@2.4.27': + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==} - '@vue/compiler-dom@3.5.13': - resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} - '@vue/compiler-sfc@3.5.13': - resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} - '@vue/compiler-ssr@3.5.13': - resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - '@vue/language-core@2.0.16': - resolution: {integrity: sha512-Bc2sexRH99pznOph8mLw2BlRZ9edm7tW51kcBXgx8adAoOcZUWJj3UNSsdQ6H9Y8meGz7BoazVrVo/jUukIsPw==} + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/language-core@2.2.8': - resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==} + '@vue/language-core@3.2.1': + resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + vue: 3.5.26 - '@vue/reactivity@3.5.13': - resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} - '@vue/runtime-core@3.5.13': - resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} - - '@vue/runtime-dom@3.5.13': - resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} - - '@vue/server-renderer@3.5.13': - resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} - peerDependencies: - vue: 3.5.13 - - '@vue/shared@3.5.13': - resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - - '@vue/test-utils@2.4.1': - resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==} - peerDependencies: - '@vue/server-renderer': ^3.0.1 - vue: ^3.0.1 - peerDependenciesMeta: - '@vue/server-renderer': - optional: true + '@vue/test-utils@2.4.6': + resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} '@webgpu/types@0.1.38': resolution: {integrity: sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==} - '@xhmikosr/archive-type@7.0.0': - resolution: {integrity: sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA==} - engines: {node: ^14.14.0 || >=16.0.0} - - '@xhmikosr/bin-check@7.0.3': - resolution: {integrity: sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA==} + '@xhmikosr/archive-type@7.1.0': + resolution: {integrity: sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==} engines: {node: '>=18'} - '@xhmikosr/bin-wrapper@13.0.5': - resolution: {integrity: sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw==} + '@xhmikosr/bin-check@7.1.0': + resolution: {integrity: sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==} engines: {node: '>=18'} - '@xhmikosr/decompress-tar@8.0.1': - resolution: {integrity: sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg==} + '@xhmikosr/bin-wrapper@13.2.0': + resolution: {integrity: sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==} engines: {node: '>=18'} - '@xhmikosr/decompress-tarbz2@8.0.2': - resolution: {integrity: sha512-p5A2r/AVynTQSsF34Pig6olt9CvRj6J5ikIhzUd3b57pUXyFDGtmBstcw+xXza0QFUh93zJsmY3zGeNDlR2AQQ==} + '@xhmikosr/decompress-tar@8.1.0': + resolution: {integrity: sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==} engines: {node: '>=18'} - '@xhmikosr/decompress-targz@8.0.1': - resolution: {integrity: sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg==} + '@xhmikosr/decompress-tarbz2@8.1.0': + resolution: {integrity: sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==} engines: {node: '>=18'} - '@xhmikosr/decompress-unzip@7.0.0': - resolution: {integrity: sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ==} + '@xhmikosr/decompress-targz@8.1.0': + resolution: {integrity: sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==} engines: {node: '>=18'} - '@xhmikosr/decompress@10.0.1': - resolution: {integrity: sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag==} + '@xhmikosr/decompress-unzip@7.1.0': + resolution: {integrity: sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==} engines: {node: '>=18'} - '@xhmikosr/downloader@15.0.1': - resolution: {integrity: sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g==} + '@xhmikosr/decompress@10.2.0': + resolution: {integrity: sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==} + engines: {node: '>=18'} + + '@xhmikosr/downloader@15.2.0': + resolution: {integrity: sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==} engines: {node: '>=18'} '@xhmikosr/os-filter-obj@3.0.0': 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'} + + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -5146,6 +5365,10 @@ packages: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -5157,6 +5380,13 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -5167,19 +5397,23 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} hasBin: true - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.5.10: - resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==} - engines: {node: '>=6.0'} + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} agent-base@4.3.0: resolution: {integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==} @@ -5189,25 +5423,17 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - agent-base@7.1.0: - resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} - engines: {node: '>= 14'} - - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - aggregate-error@5.0.0: - resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} - engines: {node: '>=18'} - - aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7: - resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7} - version: 0.1.15 + aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/1dc7f60cda78d030dadfc518a33c472202b2ef67: + resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/1dc7f60cda78d030dadfc518a33c472202b2ef67} + version: 0.1.16 engines: {vscode: ^1.83.0} ajv-draft-04@1.0.0: @@ -5238,16 +5464,19 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - alien-signals@1.0.3: - resolution: {integrity: sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==} + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} - analytics-utils@1.0.14: - resolution: {integrity: sha512-9v0kPd8v0GuBvfQcg5BO48AElaEAr9IXMAfJWXYMAhrD3QprgozEIUgMp/de0vS136PUOBB+10XQH9eBgBmfMw==} + alien-signals@3.1.2: + resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + + analytics-utils@1.1.1: + resolution: {integrity: sha512-nRybjTpRAcHVhWb1cvYaOLJaI3R79r8XjMbu5c0wd2jKmANNqSrYwybiU0X3mp+CQQdm4YiAggTXb2cIA8XhUg==} peerDependencies: '@types/dlv': ^1.0.0 - analytics@0.8.16: - resolution: {integrity: sha512-LEFQ47G9V1zVp9WIh2xhnbmSFEJq+WEzSv6voJ5uba88lefiIIYeG2nq87gFu83ocz1qtb9u7XgeaKKVBbbgWA==} + analytics@0.8.19: + resolution: {integrity: sha512-JFgasxpWFiAoqm5UHaGQv9j9OGz+f1KAeQkABYr3Z7YGhiqhQrBpPhIVAIEyttBRJZmew1QwMhN9/bOGGBnpJA==} ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} @@ -5261,14 +5490,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -5277,17 +5502,14 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@3.17.0: - resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -5299,8 +5521,8 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} @@ -5336,42 +5558,39 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} engines: {node: '>= 0.4'} - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.1: - resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} arrify@1.0.1: @@ -5387,12 +5606,12 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - asn1js@3.0.5: - resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} engines: {node: '>=12.0.0'} - assert-never@1.2.1: - resolution: {integrity: sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} @@ -5406,6 +5625,9 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + ast-v8-to-istanbul@0.3.8: + resolution: {integrity: sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -5414,14 +5636,18 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} async@0.2.10: resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} - async@3.2.4: - resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -5434,16 +5660,12 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - avvio@9.0.0: - resolution: {integrity: sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==} + avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} aws-sdk-client-mock@4.1.0: resolution: {integrity: sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==} @@ -5451,20 +5673,22 @@ packages: aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - aws4@1.12.0: - resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} axios@0.24.0: resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} - axios@1.8.4: - resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} - - b4a@1.6.4: - resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} @@ -5480,10 +5704,10 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-preset-current-node-syntax@1.0.1: - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': ^7.0.0 || ^8.0.0-0 babel-preset-jest@29.6.3: resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} @@ -5501,18 +5725,30 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.8.1: + resolution: {integrity: sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.29: + resolution: {integrity: sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==} + hasBin: true + bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - bcryptjs@2.4.3: - resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + bcryptjs@3.0.3: + resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} + hasBin: true - better-opn@3.0.2: - resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} - engines: {node: '>=12.0.0'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} bin-version-check@5.1.0: resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} @@ -5522,9 +5758,6 @@ packages: resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==} engines: {node: '>=12'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - blob-util@2.0.2: resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} @@ -5534,37 +5767,38 @@ packages: blurhash@2.0.5: resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} - bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + bn.js@4.12.2: + resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.1: + resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + bowser@2.12.1: + resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - broadcast-channel@7.1.0: - resolution: {integrity: sha512-InJljddsYWbEL8LBnopnCg+qMQp9KcowvYWOt4YWrjD5HmxzDYKdVbDS1w/ji5rFZdRD58V5UxJPtBdpEbEJYw==} + broadcast-channel@7.2.0: + resolution: {integrity: sha512-JgraikEriG/TxBUi2W/w2O0jhHjXZUtXAvCZH0Yr3whjxYVgAg0hSe6r/teM+I5H5Q/q6RhyuKdC2pHNlFyepQ==} - browser-assert@1.2.1: - resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} - - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5597,12 +5831,16 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bufferutil@4.0.9: - resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} - bullmq@5.48.1: - resolution: {integrity: sha512-WA/NlPwmxgbDsL8KGIkQvGlRkBdVAyRiypP+Witm6Tyd2PwnBQ8K62ifnlA16rF59vpnEKwO+rmh/ZM7gKmDqg==} + bullmq@5.66.3: + resolution: {integrity: sha512-ZZfFIjGv3XRcvJDi6J02SZE8/86xQHLdjiKLrtxOGH9FrBT03sE2QZks2UuvGmDSxFeSY4lSqI1ulCPgIWicqg==} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} buraha@0.0.1: resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==} @@ -5611,17 +5849,17 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + byte-counter@0.1.0: + resolution: {integrity: sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==} + engines: {node: '>=20'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cacache@18.0.0: - resolution: {integrity: sha512-I7mVOPl3PUCeRub1U8YoGz2Lqv9WOBpobZ8RyWFXmReuILz+3OAyTa5oH3QPdtKZD7N0Yk00aLfzn0qvp8dZ1w==} - engines: {node: ^16.14.0 || >=18.0.0} + cacache@20.0.3: + resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} + engines: {node: ^20.17.0 || >=22.9.0} cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} @@ -5631,29 +5869,26 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} - cacheable-request@12.0.1: - resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} + cacheable-request@13.0.15: + resolution: {integrity: sha512-NjiSrjv37X73FmGGU5ec/M83vWQ6q1Ae3BFe+ABfdeeMy4LOMKYTpfEjrBnLedu43clKZtsYbKrHTIQE7vKq+A==} engines: {node: '>=18'} - cachedir@2.3.0: - resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} + cachedir@2.4.0: + resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -5673,44 +5908,38 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001591: - resolution: {integrity: sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==} + caniuse-lite@1.0.30001755: + resolution: {integrity: sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==} - caniuse-lite@1.0.30001695: - resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} + canonicalize@2.1.0: + resolution: {integrity: sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==} + hasBin: true - canonicalize@1.0.8: - resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==} - - canvas-confetti@1.9.3: - resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==} - - canvas@3.1.0: - resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==} - engines: {node: ^18.12.0 || >= 20.9.0} + canvas-confetti@1.9.4: + resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - cbor@9.0.2: - resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==} - engines: {node: '>=16'} + cbor@10.0.11: + resolution: {integrity: sha512-vIwORDd/WyB8Nc23o2zNN5RrtFGlR6Fca61TtjkUXueI3Jf2DOZDl1zsshvBntZ3wZHBM9ztjnkXSmzQDaq3WA==} + engines: {node: '>=20'} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} - chalk-template@1.1.0: - resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + engines: {node: '>=18'} + + chalk-template@1.1.2: + resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} engines: {node: '>=14.16'} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -5719,10 +5948,13 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -5739,8 +5971,8 @@ packages: character-parser@2.2.0: resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} - chart.js@4.4.8: - resolution: {integrity: sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==} + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} engines: {pnpm: '>=8'} chartjs-adapter-date-fns@3.0.0: @@ -5749,8 +5981,8 @@ packages: chart.js: '>=2.8.0' date-fns: '>=2.0.0' - chartjs-chart-matrix@2.1.1: - resolution: {integrity: sha512-hJ5NKGYqfM37mnkr3XXIJDn9Eij4G7mbNsNxY1zEmtoVLu/k6HO9yL8sL8vFgVnJbhWqAJdlrb+dlTOFKh8xfA==} + chartjs-chart-matrix@3.0.0: + resolution: {integrity: sha512-lUWC1UaWkxGdG02dBJ5r1ppbSYB/uWmwAh11VEs7V3ZQItNCk4am+rmacwkgeb+SQeEj2hP9Qq4oGsUmPl/1lQ==} peerDependencies: chart.js: '>=3.0.0' @@ -5779,12 +6011,13 @@ packages: resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} engines: {node: '>=18.17'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + cheerio@1.1.2: + resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} + engines: {node: '>=20.18.1'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -5794,8 +6027,8 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - chromatic@11.28.0: - resolution: {integrity: sha512-Xy3907MXY5UP7LoMksmsT02xCUsoLZpcC6sRITjd+KiXBteOxPF7J+QsFqy/VzQCEN6fpt/R2bOIpE+PwPfcZA==} + chromatic@13.3.4: + resolution: {integrity: sha512-TR5rvyH0ESXobBB3bV8jc87AEAFQC7/n+Eb4XWhJz6hW3YNxIQPVjcbgLv+a4oKHEl1dUBueWSoIQsOVGTd+RQ==} hasBin: true peerDependencies: '@chromatic-com/cypress': ^0.*.* || ^1.0.0 @@ -5806,40 +6039,27 @@ packages: '@chromatic-com/playwright': optional: true - ci-info@3.7.1: - resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.1.0: - resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} - cjs-module-lexer@1.2.2: - resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clean-stack@5.2.0: - resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} - engines: {node: '>=14.16'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - cli-highlight@2.1.11: - resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - cli-table3@0.6.3: - resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} - engines: {node: 10.* || >= 12.*} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + cli-table3@0.6.1: + resolution: {integrity: sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==} engines: {node: 10.* || >= 12.*} cli-truncate@2.1.0: @@ -5860,6 +6080,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -5868,22 +6092,24 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - collect-v8-coverage@1.0.1: - resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} @@ -5898,8 +6124,15 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - colorette@2.0.19: - resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -5912,6 +6145,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -5923,10 +6160,6 @@ packages: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -5942,25 +6175,25 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compress-commons@6.0.2: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} - computeds@0.0.1: - resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - consola@3.4.0: - resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} console-control-strings@1.1.0: @@ -5973,6 +6206,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -5983,22 +6220,21 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - cookie@1.0.1: - resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-js@3.29.1: resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==} @@ -6030,47 +6266,37 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} - cropperjs@2.0.0: - resolution: {integrity: sha512-TO2j0Qre01kPHbow4FuTrbdEB4jTmGRySxW49jyEIqlJZuEBfrvCTT0vC3eRB2WBXudDfKi1Onako6DKWKxeAQ==} + cropperjs@2.1.0: + resolution: {integrity: sha512-SsSDqdVRl+mjbIBkGWlk1gCGcc+HzBqCbH5EQ+1tkAFUdxq2KUGukXF1RqhmvXrrdrX7PDwSUkWgXS7E36KvGQ==} - cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} hasBin: true - cross-fetch@3.1.6: - resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==} - - cross-fetch@4.1.0: - resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - css-declaration-sorter@7.2.0: - resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + css-declaration-sorter@7.3.0: + resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 - css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} css.escape@1.5.1: @@ -6081,81 +6307,83 @@ packages: engines: {node: '>=4'} hasBin: true - cssnano-preset-default@7.0.6: - resolution: {integrity: sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==} + cssnano-preset-default@7.0.10: + resolution: {integrity: sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - cssnano-utils@5.0.0: - resolution: {integrity: sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==} + cssnano-utils@5.0.1: + resolution: {integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - cssnano@7.0.6: - resolution: {integrity: sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==} + cssnano@7.1.2: + resolution: {integrity: sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssstyle@4.2.1: - resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} - engines: {node: '>=18'} + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - cypress@14.1.0: - resolution: {integrity: sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} - cypress@14.3.0: - resolution: {integrity: sha512-rRfPl9Z0/CczuYybBEoLbDVuT1OGkhYaJ0+urRCshgiDRz6QnoA0KQIQnPx7MJ3zy+VCsbUU1pV74n+6cbJEdg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + cssstyle@5.3.6: + resolution: {integrity: sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cypress@15.8.1: + resolution: {integrity: sha512-ogc62stTQGh1395ipKxfCE5hQuSApTzeH5e0d9U6m7wYO9HQeCpgnkYtBtd0MbkN2Fnch5Od2mX9u4hoTlrH4Q==} + engines: {node: ^20.1.0 || ^22.0.0 || >=24.0.0} hasBin: true dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} - data-uri-to-buffer@4.0.0: - resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - dayjs@1.11.10: - resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} - - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -6176,24 +6404,6 @@ packages: supports-color: optional: true - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -6203,6 +6413,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -6211,8 +6430,8 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} decode-bmp@0.2.1: resolution: {integrity: sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA==} @@ -6222,15 +6441,19 @@ packages: resolution: {integrity: sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==} engines: {node: '>=8.6'} - decode-named-character-reference@1.0.2: - resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + decompress-response@10.0.0: + resolution: {integrity: sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==} + engines: {node: '>=20'} decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@1.3.0: - resolution: {integrity: sha512-7glNLfvdsMzZm3FpRY1CHuI2lbYDR+71YmrhmTZjYFD5pfT0ACgnGRdrrC9Mk2uICnzkcdelCx5at787UDGOvg==} + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -6244,24 +6467,29 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-equal@2.2.0: - resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge@4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - defaults@3.0.0: - resolution: {integrity: sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + + defaults@2.0.2: + resolution: {integrity: sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==} + engines: {node: '>=16'} + defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -6270,13 +6498,9 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - - define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} @@ -6310,8 +6534,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-newline@3.1.0: @@ -6321,6 +6545,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-match-patch@1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} @@ -6332,8 +6559,12 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} - dijkstrajs@1.0.2: - resolution: {integrity: sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -6371,6 +6602,11 @@ packages: domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + domhandler@3.3.0: resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} engines: {node: '>= 4'} @@ -6386,14 +6622,11 @@ packages: domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - domutils@3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -6420,15 +6653,15 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.83: - resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} + electron-to-chromium@1.5.255: + resolution: {integrity: sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} - emoji-regex-xs@1.0.0: - resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -6436,6 +6669,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -6444,17 +6681,17 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding-sniffer@0.2.0: - resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + encoding-sniffer@0.2.1: + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} entities@2.2.0: @@ -6464,6 +6701,14 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -6471,19 +6716,11 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.22.1: - resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} - engines: {node: '>= 0.4'} - - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -6497,37 +6734,27 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.6.0: - resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} - - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} engines: {node: '>= 0.4'} - es-toolkit@1.27.0: - resolution: {integrity: sha512-ETSFA+ZJArcuSCpzD2TjAy6UHpx4E4uqFsoDg9F/nTLogrLmVVZQ+zNxco5h7cWnA1nNak07IXsLcaSMih+ZPQ==} + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es-toolkit@1.42.0: + resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} es6-promise@4.2.8: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} @@ -6535,30 +6762,19 @@ packages: es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - esbuild-register@3.5.0: - resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} - peerDependencies: - esbuild: '>=0.12 <1' + esbuild-plugin-swc@1.0.1: + resolution: {integrity: sha512-K/basZARuDSHH7Krr7FdzwZF4WRLTcZa4c0R2FPuXCyYdh2nZMDdi6SYuuDv0MzKQY+jm0Afl4gltLsBzPesiQ==} esbuild@0.19.11: resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} engines: {node: '>=12'} hasBin: true - esbuild@0.25.0: - resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true - esbuild@0.25.2: - resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -6589,6 +6805,11 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-formatter-pretty@4.1.0: resolution: {integrity: sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==} engines: {node: '>=10'} @@ -6596,8 +6817,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-module-utils@2.12.0: - resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -6617,8 +6838,8 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.31.0: - resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -6627,30 +6848,37 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-vue@10.0.0: - resolution: {integrity: sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w==} + eslint-plugin-vue@10.6.2: + resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: + '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 vue-eslint-parser: ^10.0.0 + peerDependenciesMeta: + '@stylistic/eslint-plugin': + optional: true + '@typescript-eslint/parser': + optional: true eslint-rule-docs@1.1.235: resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==} - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.22.0: - resolution: {integrity: sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==} + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -6659,8 +6887,11 @@ packages: jiti: optional: true - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + esm-resolve@1.0.11: + resolution: {integrity: sha512-LxF0wfUQm3ldUDHkkV2MIbvvY0TgzIpJ420jHSV1Dm+IlplBEWiJTKWM61GtxUfvjV6iD4OtTYFGAGM2uuIUWg==} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -6710,6 +6941,9 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -6726,45 +6960,40 @@ packages: resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - execa@9.5.2: - resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} executable@4.1.1: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} + exifreader@4.33.1: + resolution: {integrity: sha512-KsVc4bRfZW255PSst5Opt5jUeLp+SD2+q6fmXQkMMkphpFCDBFjzNAvswgQa1YcMrXq+9Na6HJ6gS3wo2x7RRw==} + exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - exponential-backoff@3.1.1: - resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} - - express@4.21.1: - resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} - engines: {node: '>= 0.10.0'} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -6785,8 +7014,8 @@ packages: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} - fast-content-type-parse@2.0.0: - resolution: {integrity: sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -6794,8 +7023,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-fifo@1.3.0: - resolution: {integrity: sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} @@ -6804,8 +7033,8 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-json-stringify@6.0.0: - resolution: {integrity: sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==} + fast-json-stringify@6.1.1: + resolution: {integrity: sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} @@ -6813,42 +7042,28 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} - fast-redact@3.1.2: - resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} - engines: {node: '>=6'} - fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@2.4.0: - resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-uri@3.0.1: - resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} - - fast-xml-parser@4.4.1: - resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true - fast-xml-parser@4.5.0: - resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==} - hasBin: true - - fastify-plugin@4.5.1: - resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} - - fastify-plugin@5.0.0: - resolution: {integrity: sha512-0725fmH/yYi8ugsjszLci+lLnGBK6cG+WSxM7edY2OXJEU7gr2JiGBoieL2h9mhTych1vFsEfXsAsGGDJ/Rd5w==} + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} fastify-raw-body@5.0.0: resolution: {integrity: sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==} engines: {node: '>= 10'} - fastify@5.2.2: - resolution: {integrity: sha512-22T/PnhquWozuFXg3Ish4md5ipsF1Nx1mJ9ulLdZPXSk14WFj/wMlyNB/yll9sQOojKRgOIxT2inK3Xpjg5hyw==} + fastify@5.6.2: + resolution: {integrity: sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -6856,22 +7071,26 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.4.3: - resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - feed@4.2.2: - resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} - engines: {node: '>=0.4.0'} + feed@5.1.0: + resolution: {integrity: sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg==} + engines: {node: '>=20', pnpm: '>=10'} fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -6884,10 +7103,18 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-type@19.6.0: - resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} + file-type@20.5.0: + resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} engines: {node: '>=18'} + file-type@21.1.1: + resolution: {integrity: sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==} + engines: {node: '>=20'} + + file-type@21.2.0: + resolution: {integrity: sha512-vCYBgFOrJQLoTzDyAXAL/RFfKnXXpUYt4+tipVy26nJJhT7ftgGETf2tAQF59EEL61i3MrorV/PG6tf7LJK7eg==} + engines: {node: '>=20'} + filename-reserved-regex@3.0.0: resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6904,12 +7131,13 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - find-my-way@9.0.1: - resolution: {integrity: sha512-/5NN/R0pFWuff16TMajeKt2JyiW+/OE8nOO8vo1DwZTxLaIURb7lcBYPIgRPh61yCNh9l8voeKwcrkUzmB00vw==} - engines: {node: '>=14'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} - find-package-json@1.2.0: - resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==} + find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -6923,23 +7151,24 @@ packages: resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} engines: {node: '>=12'} - fkill@9.0.0: - resolution: {integrity: sha512-MdYSsbdCaIRjzo5edthZtWmEZVMfr1qrtYZUHIdO3swCE+CoZA8S5l0s4jDsYlTa9ZiXv0pTgpzE7s4N8NeUOA==} - engines: {node: '>=18'} + fkill@10.0.1: + resolution: {integrity: sha512-ecaskrAMoRXdtrGEQI/NyEJ3ZoZrMUVPb3EPNMrSqP/Tb08ZN22KqMJN24cDgNJ+ddkQR8W6cJRMvF9+nnZg6A==} + engines: {node: '>=20'} flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} fluent-ffmpeg@2.1.3: resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==} engines: {node: '>=18'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -6947,20 +7176,12 @@ packages: debug: optional: true - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} forever-agent@0.6.1: @@ -6970,18 +7191,22 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data-encoder@4.0.2: - resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==} + form-data-encoder@4.1.0: + resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} engines: {node: '>= 18'} - form-data@4.0.2: - resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -6993,14 +7218,15 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + from@0.1.7: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} fs-extra@8.1.0: @@ -7030,12 +7256,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -7046,6 +7268,10 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -7054,13 +7280,9 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} - engines: {node: '>= 0.4'} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} @@ -7082,37 +7304,20 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-stream@9.0.1: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.10.0: - resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} - - get-tsconfig@4.9.0: - resolution: {integrity: sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==} - - getos@3.2.1: - resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -7126,53 +7331,45 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true - glob@11.0.1: - resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} hasBin: true + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported - global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@16.0.0: - resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - google-protobuf@3.21.2: - resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} - - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + google-protobuf@3.21.4: + resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -7182,8 +7379,8 @@ packages: resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} engines: {node: '>=16'} - got@14.4.7: - resolution: {integrity: sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==} + got@14.6.5: + resolution: {integrity: sha512-Su87c0NNeg97de1sO02gy9I8EmE7DCJ1gzcFLcgGpYeq2PnLg4xz73MWrp6HjqbSsjb6Glf4UBDW6JNyZA6uSg==} engines: {node: '>=20'} graceful-fs@4.2.11: @@ -7192,28 +7389,28 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + graphql@16.12.0: + resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} hammerjs@2.0.8: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} - happy-dom@16.8.1: - resolution: {integrity: sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==} - engines: {node: '>=18.0.0'} - - happy-dom@17.4.4: - resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==} - engines: {node: '>=18.0.0'} + happy-dom@20.0.11: + resolution: {integrity: sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==} + engines: {node: '>=20.0.0'} hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + harfbuzzjs@0.4.14: + resolution: {integrity: sha512-f/J2RjGxuY2ErVp3ECqUryZnwmXBYYjH8VnV7cO6WOxRr57/HrtDBkhhFP2aje5Q5xm9D2P4S6WQY0pL2h3f7w==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -7223,32 +7420,17 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -7256,16 +7438,12 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} + hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} @@ -7281,14 +7459,11 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - headers-polyfill@4.0.2: - resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - highlight.js@11.10.0: - resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} hosted-git-info@2.8.9: @@ -7302,12 +7477,16 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - html-entities@2.5.2: - resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -7315,21 +7494,20 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - htmlescape@1.1.1: - resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} - engines: {node: '>=0.10'} + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} htmlparser2@5.0.1: resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} - htmlparser2@8.0.1: - resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} htmlparser2@9.1.0: resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} - http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -7339,6 +7517,10 @@ packages: resolution: {integrity: sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==} engines: {node: '>=6.0.0'} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -7363,10 +7545,6 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.2: - resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} - engines: {node: '>= 14'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -7383,12 +7561,8 @@ packages: resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} engines: {node: '>=12.20.0'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - human-signals@8.0.0: - resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} iconv-lite@0.4.24: @@ -7399,8 +7573,12 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - idb-keyval@6.2.1: - resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -7408,30 +7586,34 @@ packages: ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - ignore-walk@7.0.0: - resolution: {integrity: sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==} - engines: {node: ^18.17.0 || >=20.5.0} + ignore-walk@8.0.0: + resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} + engines: {node: ^20.17.0 || >=22.9.0} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@5.0.3: - resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@1.11.2: - resolution: {integrity: sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==} + import-in-the-middle@2.0.0: + resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} - import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} hasBin: true @@ -7443,9 +7625,9 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -7467,25 +7649,29 @@ packages: inspect-with-kind@1.0.5: resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} - install-artifact-from-github@1.3.5: - resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==} + install-artifact-from-github@1.4.0: + resolution: {integrity: sha512-+y6WywKZREw5rq7U2jvr2nmZpT7cbWbQQ0N/qfcseYnzHFz2cZz1Et52oY+XttYuYeTkI8Y+R2JNWj68MpQFSg==} hasBin: true - internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} - - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} 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.6.0: - resolution: {integrity: sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==} + ioredis@5.8.2: + resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} engines: {node: '>=12.22.0'} + ios-haptics@0.1.4: + resolution: {integrity: sha512-94FJcSuvmhe4mHTX4Uauj+/2yhs56m4BLkRScy1vNvkv7H1cSjsvfT+olc1sYOUqWlPhtVttAFBHex0cY9CE7Q==} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -7498,64 +7684,63 @@ packages: resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} irregular-plurals@3.5.0: resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} engines: {node: '>=8'} - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true is-expression@4.0.0: @@ -7568,6 +7753,10 @@ packages: is-file-animated@1.0.2: resolution: {integrity: sha512-TAYDUkvyBmxqneRU26zzpeHLAgtzEOIsRQWrtDidPT/tFK3Yc0WKgtF3u4oOEAiN0kAuVfl7MTgbD0vXdFDztA==} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -7576,26 +7765,25 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} - is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - - is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - - is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} is-negative-zero@2.0.3: @@ -7605,8 +7793,8 @@ packages: is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} is-number@7.0.0: @@ -7635,18 +7823,19 @@ packages: is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} - is-set@2.0.2: - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} is-stream@2.0.1: @@ -7661,24 +7850,20 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-svg@5.1.0: - resolution: {integrity: sha512-uVg5yifaTxHoefNf5Jcx+i9RZe2OBYd/UStp1umx+EERa4xGRa3LLGXjoEph43qUORC0qkafUgrXZ6zzK89yGA==} - engines: {node: '>=14.16'} + is-svg@6.1.0: + resolution: {integrity: sha512-i7YPdvYuSCYcaLQrKwt8cvKTlwHcdA6Hp8N9SO3Q5jIzo8x6kH3N47W0BvPP7NdxVBmIHx7X9DK36czYYW7lHg==} + engines: {node: '>=20'} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} - is-typed-array@1.1.10: - resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: @@ -7692,18 +7877,21 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} - is-weakmap@2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} - is-weakset@2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -7729,8 +7917,8 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} - istanbul-lib-instrument@6.0.0: - resolution: {integrity: sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} engines: {node: '>=10'} istanbul-lib-report@3.0.1: @@ -7745,8 +7933,8 @@ packages: resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} iterare@1.2.1: @@ -7760,8 +7948,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.0.1: - resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} jest-changed-files@29.7.0: @@ -7806,13 +7994,19 @@ packages: resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-fetch-mock@3.0.3: - resolution: {integrity: sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==} - jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7850,6 +8044,10 @@ packages: resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-resolve-dependencies@29.7.0: resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7882,9 +8080,6 @@ packages: resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-websocket-mock@2.5.0: - resolution: {integrity: sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==} - jest-worker@29.7.0: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7902,29 +8097,41 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - joi@17.11.0: - resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==} - joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - js-beautify@1.14.9: - resolution: {integrity: sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==} - engines: {node: '>=12'} + joi@18.0.1: + resolution: {integrity: sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==} + engines: {node: '>= 20'} + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-stringify@1.0.2: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsbn@0.1.1: @@ -7937,22 +8144,27 @@ packages: resolution: {integrity: sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==} engines: {node: '>=0.1.90'} - jsdoc-type-pratt-parser@4.1.0: - resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} - engines: {node: '>=12.0.0'} + jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true - jsdom@26.0.0: - resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} - engines: {node: '>=18'} + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: canvas: optional: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: @@ -7961,8 +8173,8 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-ref-resolver@1.0.1: - resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -7988,8 +8200,8 @@ packages: engines: {node: '>=6'} hasBin: true - jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -7997,12 +8209,12 @@ packages: jsonfile@5.0.0: resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonld@8.3.3: - resolution: {integrity: sha512-9YcilrF+dLfg9NTEof/mJLMtbdX1RJ8dbWtJgE00cMOIohb1lIyJl710vFiTaiHTl6ZYODJuBd32xFvUhmv3kg==} - engines: {node: '>=14'} + jsonld@9.0.0: + resolution: {integrity: sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==} + engines: {node: '>=18'} jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} @@ -8016,22 +8228,19 @@ packages: resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} engines: {'0': node >=0.6.0} - jsrsasign@11.1.0: - resolution: {integrity: sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==} - jstransformer@1.0.0: resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} - juice@11.0.1: - resolution: {integrity: sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==} + juice@11.0.3: + resolution: {integrity: sha512-VYjPg4WylyWyLPnSiUsJ9tnnGhRZF0vn0YD8WWwaI8FhP9+1UdRMyRDbvqPOH/nBotmLKOc+FI+Oma6FwVWfSw==} engines: {node: '>=18.17'} hasBin: true just-extend@6.2.0: resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} - jwa@2.0.0: - resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} @@ -8039,6 +8248,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.5.4: + resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -8047,19 +8259,9 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - ky-universal@0.11.0: - resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==} - engines: {node: '>=14.16'} - peerDependencies: - ky: '>=0.31.4' - web-streams-polyfill: '>=3.2.1' - peerDependenciesMeta: - web-streams-polyfill: - optional: true - - ky@0.33.3: - resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} - engines: {node: '>=14.16'} + ky@1.14.0: + resolution: {integrity: sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==} + engines: {node: '>=18'} lazy-ass@1.6.0: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} @@ -8077,8 +8279,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - light-my-request@6.0.0: - resolution: {integrity: sha512-kFkFXrmKCL0EEeOmJybMH5amWFd+AFvlvMlvFTRxCUwbhfapZqDmeLMPoWihntnYY6JpoQDE9k+vOzObF1fDqg==} + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} @@ -8096,6 +8298,10 @@ packages: enquirer: optional: true + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8107,10 +8313,6 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - lodash.get@4.4.2: - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. - lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} @@ -8143,8 +8345,8 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} @@ -8153,8 +8355,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.0.0: - resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -8164,27 +8366,23 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - lru-cache@8.0.4: - resolution: {integrity: sha512-E9FF6+Oc/uFLqZCuZwRKUzgFt5Raih6LfxknOSAVTjNkrCZkBf7DQCwJxZQgd9l4eHjIJDGR+E+1QKD1RhThPw==} + lru-cache@8.0.5: + resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} engines: {node: '>=16.14'} - luxon@3.3.0: - resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==} + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} engines: {node: '>=12'} lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} @@ -8200,9 +8398,9 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - make-fetch-happen@13.0.0: - resolution: {integrity: sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==} - engines: {node: ^16.14.0 || >=18.0.0} + make-fetch-happen@15.0.3: + resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} + engines: {node: ^20.17.0 || >=22.9.0} makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -8221,8 +8419,8 @@ packages: map-stream@0.1.0: resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - markdown-table@3.0.3: - resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} @@ -8231,17 +8429,17 @@ packages: matter-js@0.20.0: resolution: {integrity: sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==} - mdast-util-find-and-replace@3.0.1: - resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.0: - resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} - mdast-util-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - mdast-util-gfm-footnote@2.0.0: - resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} mdast-util-gfm-strikethrough@2.0.0: resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} @@ -8252,8 +8450,8 @@ packages: mdast-util-gfm-task-list-item@2.0.0: resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - mdast-util-gfm@3.0.0: - resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} @@ -8261,8 +8459,8 @@ packages: mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - mdast-util-to-markdown@2.1.0: - resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -8270,15 +8468,22 @@ packages: mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - meilisearch@0.49.0: - resolution: {integrity: sha512-oMJ/e6Or6cz2+owcEKeB11p2OWiWW9NmssqOZC/KIwQB0sBGKLJ7RCpYzf+GhUIZIZ9FRYZ419ox3RGebVQX5g==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + mediabunny@1.27.2: + resolution: {integrity: sha512-0g/vmb6X0xmnzqW0U44weF9CmzcUFFQRHrjzoVpSL2KXuhWIWcW3u9wIcohHiTY78jfr0SauYKLxjOQReaMxXQ==} + + meilisearch@0.54.0: + resolution: {integrity: sha512-b1bwJAEfj8C6hgSN88+/LvW3pe3nWC+thBS2seAbPZGakf/vzsLqppgZquiomzCr2GhU7U7H289625qhYe3rbw==} memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -8293,6 +8498,10 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -8304,96 +8513,92 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - mfm-js@0.24.0: - resolution: {integrity: sha512-6m8N0ElH9/4CA1izhVqmxTfLj5Z9RspdqM/lMew4xU/UTgm4Pf//VpDunpasxbRFjeJSVW+zoVwL4ZPfPtfiQg==} + mfm-js@0.25.0: + resolution: {integrity: sha512-JoK5TOtswXIvZSZ9hUEL+ZkcNV4onu/DtkaKeXK846+sJBBF8DvxYmPutt7nPaRDsUMmJGr64PNVMFpMGdk3hw==} - microformats-parser@2.0.2: - resolution: {integrity: sha512-tUf9DmN4Jq/tGyp1YH2V6D/Cud+9Uc0WhjjUFirqVeHTRkkfLDacv6BQFT7h7HFsD0Z8wja5eKkRgzZU8bv0Fw==} - engines: {node: '>=18'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - micromark-core-commonmark@2.0.0: - resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - micromark-extension-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==} + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - micromark-extension-gfm-footnote@2.0.0: - resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==} + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - micromark-extension-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==} - - micromark-extension-gfm-table@2.0.0: - resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==} + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} micromark-extension-gfm-tagfilter@2.0.0: resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - micromark-extension-gfm-task-list-item@2.0.1: - resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==} + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - micromark-factory-destination@2.0.0: - resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - micromark-factory-label@2.0.0: - resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - micromark-factory-space@2.0.0: - resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - micromark-factory-title@2.0.0: - resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - micromark-factory-whitespace@2.0.0: - resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - micromark-util-character@2.1.0: - resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - micromark-util-chunked@2.0.0: - resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - micromark-util-classify-character@2.0.0: - resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - micromark-util-combine-extensions@2.0.0: - resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - micromark-util-decode-numeric-character-reference@2.0.1: - resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - micromark-util-decode-string@2.0.0: - resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - micromark-util-html-tag-name@2.0.0: - resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - micromark-util-normalize-identifier@2.0.0: - resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - micromark-util-resolve-all@2.0.0: - resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - micromark-util-subtokenize@2.0.0: - resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - micromark@4.0.0: - resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} @@ -8403,10 +8608,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -8445,18 +8658,19 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.0.1: - resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} - minimatch@3.0.8: - resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.2: - resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} minimatch@9.0.1: @@ -8478,13 +8692,13 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} - minipass-fetch@3.0.3: - resolution: {integrity: sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + minipass-fetch@5.0.0: + resolution: {integrity: sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==} + engines: {node: ^20.17.0 || >=22.9.0} minipass-flush@1.0.5: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} @@ -8514,13 +8728,10 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - minizlib@3.0.1: - resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8530,48 +8741,37 @@ packages: engines: {node: '>=10'} hasBin: true - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - - mnemonist@0.40.0: - resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==} - mock-socket@9.3.1: 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==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - ms@3.0.0-canary.1: - resolution: {integrity: sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==} - engines: {node: '>=12.13'} + ms@3.0.0-canary.202508261828: + resolution: {integrity: sha512-NotsCoUCIUkojWCzQff4ttdCfIPoA1UGZsyQbi7KmqkNRfKCrvga8JJi2PknHymHOuor0cJSn/ylj52Cbt2IrQ==} + engines: {node: '>=18'} - msgpackr-extract@3.0.2: - resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.11.2: - resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} + msgpackr@1.11.5: + resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} - msw-storybook-addon@2.0.4: - resolution: {integrity: sha512-rstO8+r01sRMg6PPP7OxM8LG5/6r4+wmp2uapHeHvm9TQQRHvpPXOU/Y9/Somysz8Oi4Ea1aummXH3JlnP2LIA==} + msw-storybook-addon@2.0.6: + resolution: {integrity: sha512-ExCwDbcJoM2V3iQU+fZNp+axVfNc7DWMRh4lyTXebDO8IbpUNYKGFUrA8UqaeWiRGKVuS7+fU+KXEa9b0OP6uA==} peerDependencies: msw: ^2.0.0 - msw@2.7.3: - resolution: {integrity: sha512-+mycXv8l2fEAjFZ5sjrtjJDmm2ceKGjrNbBr1durRg6VkU9fNUE/gsmQ51hWbHqs+l35W1iM+ZsmOD9Fd6lspw==} + msw@2.12.6: + resolution: {integrity: sha512-nGhVcHP64skBrJbn1RVO/573cq8vgoce19Ljm60xp66RDpcPrX96ik5oMf3tIIku2kc31plyI2i0vd2J6GPMVg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -8583,37 +8783,31 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - multer@1.4.4-lts.1: - resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} - engines: {node: '>= 6.0.0'} + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - mylas@2.1.13: - resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} - engines: {node: '>=12.0.0'} + mylas@2.1.14: + resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} + engines: {node: '>=16.0.0'} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nan@2.24.0: + resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} - nan@2.20.0: - resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==} - - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -8630,6 +8824,10 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + nested-property@4.0.0: resolution: {integrity: sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==} @@ -8637,33 +8835,23 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - nice-napi@1.0.2: - resolution: {integrity: sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==} - os: ['!win32'] - nise@6.1.1: resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==} - node-abi@3.62.0: - resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} - engines: {node: '>=10'} - - node-abi@3.74.0: - resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} engines: {node: '>=10'} node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - node-addon-api@3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch@2.6.13: resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==} @@ -8687,27 +8875,30 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-gyp-build-optional-packages@5.0.7: - resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true - node-gyp-build@4.6.0: - resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@10.2.0: - resolution: {integrity: sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==} - engines: {node: ^16.14.0 || >=18.0.0} + node-gyp@12.1.0: + resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true + node-html-parser@7.0.1: + resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nodemailer@6.10.0: - resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==} + nodemailer@7.0.12: + resolution: {integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==} engines: {node: '>=6.0.0'} nodemon@3.0.2: @@ -8715,8 +8906,8 @@ packages: engines: {node: '>=10'} hasBin: true - nodemon@3.1.9: - resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==} + nodemon@3.1.11: + resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==} engines: {node: '>=10'} hasBin: true @@ -8724,23 +8915,19 @@ packages: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} engines: {node: '>=12.19'} - nopt@1.0.10: - resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} - hasBin: true - nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} hasBin: true - nopt@6.0.0: - resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true - nopt@7.2.0: - resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true normalize-package-data@2.5.0: @@ -8754,8 +8941,8 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-url@8.0.1: - resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + normalize-url@8.1.0: + resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} engines: {node: '>=14.16'} npm-run-path@4.0.1: @@ -8783,8 +8970,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.16: - resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} oauth2orize-pkce@0.1.2: resolution: {integrity: sha512-grto2UYhXHi9GLE3IBgBBbV87xci55+bCyjpVuxKyzol6I5Rg0K1MiTuXE+JZk54R86SG2wqXODMiZYHraPpxw==} @@ -8793,35 +8980,24 @@ packages: resolution: {integrity: sha512-j4XtFDQUBsvUHPjUmvmNDUDMYed2MphMIJBhyxVVe8hGCjkuYnjIsW+D9qk8c5ciXRdnk6x6tEbiO6PLeOZdCQ==} engines: {node: '>= 0.4.0'} - oauth@0.10.2: - resolution: {integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} object.fromentries@2.0.8: @@ -8832,22 +9008,20 @@ packages: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - obliterator@2.0.4: - resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} - - oblivious-set@1.4.0: - resolution: {integrity: sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg==} + oblivious-set@2.0.0: + resolution: {integrity: sha512-QOUH5Xrsced9fKXaQTjWoDGKeS/Or7E2jB0FN63N4mkAO4qJdB7WR7e6qWAOHM5nk25FJ8TGjhP7DH4l6vFVLg==} engines: {node: '>=16'} - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - on-exit-leak-free@2.1.0: - resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -8864,22 +9038,24 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - oniguruma-parser@0.5.4: - resolution: {integrity: sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==} + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} - oniguruma-to-es@4.1.0: - resolution: {integrity: sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==} + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - openapi-typescript@6.7.6: - resolution: {integrity: sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==} + openapi-typescript@7.10.1: + resolution: {integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==} hasBin: true + peerDependencies: + typescript: ^5.x optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -8891,12 +9067,16 @@ packages: ospath@1.2.2: resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - otpauth@9.4.0: - resolution: {integrity: sha512-fHIfzIG5RqCkK9cmV8WU+dPQr9/ebR5QOwGZn2JAr1RQF+lmAuLL2YdtdqvmBjNmgJlYk3KZ4a0XokaEhg1Jsw==} + otpauth@9.4.1: + resolution: {integrity: sha512-+iVvys36CFsyXEqfNftQm1II7SW23W1wx9RwNk0Cd97lbvorqAhBDksb/0bYry087QMxjiuBS0wokdoZ0iUeAw==} outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -8929,6 +9109,10 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + engines: {node: '>=18'} + p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} @@ -8941,8 +9125,8 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -8952,6 +9136,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -8959,23 +9147,17 @@ packages: parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} - parse5-htmlparser2-tree-adapter@6.0.1: - resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} - - parse5-htmlparser2-tree-adapter@7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} parse5-parser-stream@7.1.2: resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} - parse5@5.1.1: - resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - - parse5@7.2.1: - resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -9007,22 +9189,18 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} - path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -9031,62 +9209,44 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - peek-readable@5.3.1: - resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} - engines: {node: '>=14.16'} - pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} - pg-connection-string@2.7.0: - resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-numeric@1.0.2: - resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} - engines: {node: '>=4'} - - pg-pool@3.8.0: - resolution: {integrity: sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==} + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} peerDependencies: pg: '>=8.0' - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} - - pg-protocol@1.7.1: - resolution: {integrity: sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==} - - pg-protocol@1.8.0: - resolution: {integrity: sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==} + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg-types@4.0.1: - resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} - engines: {node: '>=10'} - - pg@8.14.1: - resolution: {integrity: sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==} - engines: {node: '>= 8.0.0'} + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' peerDependenciesMeta: @@ -9107,290 +9267,267 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pid-port@1.0.2: - resolution: {integrity: sha512-Khqp07zX8IJpmIg56bHrLxS3M0iSL4cq6wnMq8YE7r/hSw3Kn4QxYS6QJg8Bs22Z7CSVj7eSsxFuigYVIFWmjg==} - engines: {node: '>=18'} + pid-port@2.0.0: + resolution: {integrity: sha512-EDmfRxLl6lkhPjDI+19l5pkII89xVsiCP3aGjS808f7M16DyCKSXEWthD/hjyDLn5I4gKqTVw7hSgdvdXRJDTw==} + engines: {node: '>=20'} pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pino-abstract-transport@1.2.0: - resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - pino@9.2.0: - resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==} + pino@10.1.0: + resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} hasBin: true - pirates@4.0.5: - resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - piscina@4.4.0: - resolution: {integrity: sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==} + piscina@4.9.2: + resolution: {integrity: sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==} - pkce-challenge@4.1.0: - resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - plimit-lit@1.5.0: - resolution: {integrity: sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==} + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} plur@4.0.0: resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==} engines: {node: '>=10'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} - pnpm@10.6.1: - resolution: {integrity: sha512-QO4Jr0B/qfu1+/uOHLQPu3TArww+EOkiTXtTx2WFKGFbLJJFDnTPrZHjotyv485AUNgL2nHXV3VtLOK2YhPpow==} + pnpm@10.27.0: + resolution: {integrity: sha512-ctaZ2haxF5wUup5k3HHJpAmIy9xlwmTLDkidt96RfyDc9NZNhyNiXylpulLUt+KhFwaC2awqXcrqq3MrfhbwSg==} engines: {node: '>=18.12'} hasBin: true - polished@4.2.2: - resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==} + polished@4.3.1: + resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-calc@10.1.0: - resolution: {integrity: sha512-uQ/LDGsf3mgsSUEXmAt3VsCSHR3aKqtEIkmB+4PhzYwRYOW5MZs/GhCCFpsOtJJkP6EC6uGipbrnaTjqaJZcJw==} + postcss-calc@10.1.1: + resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==} engines: {node: ^18.12 || ^20.9 || >=22.0} peerDependencies: postcss: ^8.4.38 - postcss-colormin@7.0.2: - resolution: {integrity: sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==} + postcss-colormin@7.0.5: + resolution: {integrity: sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-convert-values@7.0.4: - resolution: {integrity: sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==} + postcss-convert-values@7.0.8: + resolution: {integrity: sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-discard-comments@7.0.3: - resolution: {integrity: sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==} + postcss-discard-comments@7.0.5: + resolution: {integrity: sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-discard-duplicates@7.0.1: - resolution: {integrity: sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==} + postcss-discard-duplicates@7.0.2: + resolution: {integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-discard-empty@7.0.0: - resolution: {integrity: sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==} + postcss-discard-empty@7.0.1: + resolution: {integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-discard-overridden@7.0.0: - resolution: {integrity: sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==} + postcss-discard-overridden@7.0.1: + resolution: {integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-merge-longhand@7.0.4: - resolution: {integrity: sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==} + postcss-merge-longhand@7.0.5: + resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-merge-rules@7.0.4: - resolution: {integrity: sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==} + postcss-merge-rules@7.0.7: + resolution: {integrity: sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-minify-font-values@7.0.0: - resolution: {integrity: sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==} + postcss-minify-font-values@7.0.1: + resolution: {integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-minify-gradients@7.0.0: - resolution: {integrity: sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==} + postcss-minify-gradients@7.0.1: + resolution: {integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-minify-params@7.0.2: - resolution: {integrity: sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==} + postcss-minify-params@7.0.5: + resolution: {integrity: sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-minify-selectors@7.0.4: - resolution: {integrity: sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==} + postcss-minify-selectors@7.0.5: + resolution: {integrity: sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-charset@7.0.0: - resolution: {integrity: sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==} + postcss-normalize-charset@7.0.1: + resolution: {integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-display-values@7.0.0: - resolution: {integrity: sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==} + postcss-normalize-display-values@7.0.1: + resolution: {integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-positions@7.0.0: - resolution: {integrity: sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==} + postcss-normalize-positions@7.0.1: + resolution: {integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-repeat-style@7.0.0: - resolution: {integrity: sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==} + postcss-normalize-repeat-style@7.0.1: + resolution: {integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-string@7.0.0: - resolution: {integrity: sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==} + postcss-normalize-string@7.0.1: + resolution: {integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-timing-functions@7.0.0: - resolution: {integrity: sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==} + postcss-normalize-timing-functions@7.0.1: + resolution: {integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-unicode@7.0.2: - resolution: {integrity: sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==} + postcss-normalize-unicode@7.0.5: + resolution: {integrity: sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-url@7.0.0: - resolution: {integrity: sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==} + postcss-normalize-url@7.0.1: + resolution: {integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-normalize-whitespace@7.0.0: - resolution: {integrity: sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==} + postcss-normalize-whitespace@7.0.1: + resolution: {integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-ordered-values@7.0.1: - resolution: {integrity: sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==} + postcss-ordered-values@7.0.2: + resolution: {integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-reduce-initial@7.0.2: - resolution: {integrity: sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==} + postcss-reduce-initial@7.0.5: + resolution: {integrity: sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-reduce-transforms@7.0.0: - resolution: {integrity: sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==} + postcss-reduce-transforms@7.0.1: + resolution: {integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} engines: {node: '>=4'} - postcss-selector-parser@7.0.0: - resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} - engines: {node: '>=4'} - - postcss-svgo@7.0.1: - resolution: {integrity: sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==} + postcss-svgo@7.1.0: + resolution: {integrity: sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==} engines: {node: ^18.12.0 || ^20.9.0 || >= 18} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 - postcss-unique-selectors@7.0.3: - resolution: {integrity: sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==} + postcss-unique-selectors@7.0.4: + resolution: {integrity: sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} - engines: {node: '>=12'} - postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} - postgres-bytea@3.0.0: - resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} - engines: {node: '>= 6'} - postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - postgres-date@2.0.1: - resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==} - engines: {node: '>=12'} - postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - postgres-interval@3.0.0: - resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} - engines: {node: '>=12'} - - postgres-range@1.1.3: - resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} - - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - hasBin: true - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -9406,8 +9543,8 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-ms@9.2.0: - resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} private-ip@3.0.2: @@ -9417,9 +9554,9 @@ packages: probe-image-size@7.2.3: resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==} - proc-log@4.2.0: - resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} process-exists@5.0.0: resolution: {integrity: sha512-6QPRh5fyHD8MaXr4GYML8K/YY0Sq5dKHGIOrAKS3cYpHQdmygFCcijIu1dVoNKAZ0TWAMoeh8KDK9dF8auBkJA==} @@ -9428,11 +9565,11 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process-warning@3.0.0: - resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} - process-warning@4.0.0: - resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} @@ -9445,9 +9582,6 @@ packages: promise-limit@2.7.0: resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} - promise-polyfill@8.3.0: - resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==} - promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -9459,8 +9593,8 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - property-information@7.0.0: - resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -9479,6 +9613,10 @@ packages: resolution: {integrity: sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + ps-list@9.0.0: + resolution: {integrity: sha512-lxMEoIL/BQlk2KunFzxwUPwMvjFH7x7cmvzSLsSHpyMXl9FFfLUlfKrYwFc4wx/ZaIxxuXC4n8rjQ1CX/tkXVQ==} + engines: {node: '>=20'} + ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -9496,9 +9634,6 @@ packages: pug-code-gen@3.0.3: resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==} - pug-error@2.0.0: - resolution: {integrity: sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==} - pug-error@2.1.0: resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} @@ -9529,11 +9664,8 @@ packages: pug@3.0.3: resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} @@ -9543,15 +9675,25 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.0.0: - resolution: {integrity: sha512-rLSBxJjP+4DQOgcJAx6RZHT2he2pkhQdSnofG5VWyVl6GRq/K02ISOuOLcsMOrtKDIJb8JN2zm3FFzWNbezdPw==} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - pvtsutils@1.3.5: - resolution: {integrity: sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} - pvutils@1.1.3: - resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} - engines: {node: '>=6.0.0'} + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + + qr-code-styling@1.9.2: + resolution: {integrity: sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==} + engines: {node: '>=18.18.0'} + + qr-scanner@1.4.2: + resolution: {integrity: sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==} + + qrcode-generator@1.5.2: + resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==} qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} @@ -9562,10 +9704,6 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - qs@6.13.1: - resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} - engines: {node: '>=0.6'} - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -9573,15 +9711,13 @@ packages: querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-lit@1.5.0: - resolution: {integrity: sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA==} + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -9608,43 +9744,39 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + rdf-canonize@5.0.0: + resolution: {integrity: sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==} + engines: {node: '>=18'} - rdf-canonize@3.4.0: - resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==} - engines: {node: '>=12'} + re2@1.23.0: + resolution: {integrity: sha512-mT7+/Lz+Akjm/C/X6PiaHihcJL92TNNXai/C4c/dfBbhtwMm1uKEEoA2Lz/FF6aBFfQzg5mAyv4BGjM4q44QwQ==} - re2@1.21.4: - resolution: {integrity: sha512-MVIfXWJmsP28mRsSt8HeL750ifb8H5+oF2UDIxGaiJCr8fkMqhLZ7kcX9ADRk2dC8qeGKedB7UVYRfBVpEiLfA==} - - react-docgen-typescript@2.2.2: - resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} peerDependencies: typescript: '>= 4.3.x' - react-docgen@7.0.1: - resolution: {integrity: sha512-rCz0HBIT0LWbIM+///LfRrJoTKftIzzwsYDf0ns5KwaEjejMHQRtphcns+IXFHDNY9pnz6G8l/JbbI6pD4EAIA==} - engines: {node: '>=16.14.0'} + react-docgen@8.0.2: + resolution: {integrity: sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==} + engines: {node: ^20.9.0 || >=22} - react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: - react: ^19.1.0 + react: ^19.2.3 react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} read-pkg-up@7.0.1: @@ -9655,8 +9787,8 @@ packages: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} - readable-stream@2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} readable-stream@3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} @@ -9666,23 +9798,23 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readable-stream@4.3.0: - resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdir-glob@1.1.2: - resolution: {integrity: sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - recast@0.23.6: - resolution: {integrity: sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} reconnecting-websocket@4.4.0: @@ -9696,13 +9828,6 @@ packages: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} - redis-info@3.1.0: - resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==} - - redis-lock@0.1.4: - resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==} - engines: {node: '>=0.6'} - redis-parser@3.0.0: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} @@ -9710,12 +9835,13 @@ packages: reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regenerator-runtime@0.14.0: - resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -9725,16 +9851,12 @@ packages: regex@6.0.1: resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} - regexp.prototype.flags@1.5.0: - resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} - - remark-gfm@4.0.0: - resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -9756,9 +9878,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@7.3.0: - resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==} - engines: {node: '>=8.6.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -9784,18 +9906,23 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve.exports@2.0.0: - resolution: {integrity: sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==} + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} hasBin: true responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} + responselike@4.0.2: + resolution: {integrity: sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==} + engines: {node: '>=20'} + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -9808,8 +9935,11 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + rettime@0.7.0: + resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rfdc@1.4.1: @@ -9825,33 +9955,30 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - - rollup@4.39.0: - resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} rss-parser@3.13.0: resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.0.0: - resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} - engines: {node: '>=0.4'} - - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -9860,46 +9987,47 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} - safe-regex2@4.0.0: - resolution: {integrity: sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==} + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} - safe-stable-stringify@2.4.2: - resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-html@2.15.0: - resolution: {integrity: sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==} + sanitize-html@2.17.0: + resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==} - sass@1.86.3: - resolution: {integrity: sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==} + sass@1.97.1: + resolution: {integrity: sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==} engines: {node: '>=14.0.0'} hasBin: true - sax@1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} - secure-json-parse@3.0.2: - resolution: {integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} @@ -9916,8 +10044,8 @@ packages: resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==} engines: {node: '>=12'} - semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true semver@6.3.1: @@ -9929,13 +10057,8 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -9943,15 +10066,23 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.6.0: - resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -9961,18 +10092,23 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} hasBin: true - sharp@0.34.1: - resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -9983,11 +10119,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@3.2.2: - resolution: {integrity: sha512-0qWBkM2t/0NXPRcVgtLhtHv6Ak3Q5yI4K/ggMqcgLRKm4+pCs3namgZlhlat/7u2CuqNtlShNs9lENOG6n7UaQ==} - - shimmer@1.2.1: - resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + shiki@3.20.0: + resolution: {integrity: sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -10001,10 +10134,6 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} @@ -10019,17 +10148,11 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-oauth2@5.1.0: resolution: {integrity: sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} @@ -10087,24 +10210,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] slacc-linux-arm64-musl@0.0.10: resolution: {integrity: sha512-3lUX7752f6Okn54aONioaA+9M5TvifqXBAart+u2lNXEdWmmh003cVSU2Vcwg7nJ9lLHtju2DkDmKKfJjFuShA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] slacc-linux-x64-gnu@0.0.10: resolution: {integrity: sha512-BxxvylF9zlOLRLCpiyMvKTIUpdLlpetNBJ+DSMDh5+Ggq+AmQz2NUGawmcBJw58F8nMCj9TpWLlGNWc2AuY+JQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] slacc-linux-x64-musl@0.0.10: resolution: {integrity: sha512-TYJi8LOtJiTFcZvka4du7bMjF9Bz1RHRwyLnScr5E5yjjgoLRrsvgSu7bxp87xH+rgJ3CdEwE3w3Ux8EiewHpA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] slacc-win32-arm64-msvc@0.0.10: resolution: {integrity: sha512-1CHPLiDB4exzFyT5ndtJDsRRhBxNg8mGz6I6eJEMjelGkJR2KZPT9LZuby/1bS/bcVOr7zuJvGNfbEGBeHRwPQ==} @@ -10141,16 +10268,16 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socks-proxy-agent@8.0.2: - resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==} + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.7.1: - resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} - engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sonic-boom@4.0.1: - resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} @@ -10160,9 +10287,6 @@ packages: resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} engines: {node: '>=0.10.0'} - sortablejs@1.14.0: - resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -10177,27 +10301,27 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spdx-correct@3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - spdx-exceptions@2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.12: - resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - split2@4.1.0: - resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} split@0.3.3: @@ -10209,23 +10333,18 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sql-highlight@6.0.0: - resolution: {integrity: sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==} + sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} engines: {node: '>=14'} - sshpk@1.17.0: - resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} - engines: {node: '>=0.10.0'} - hasBin: true - sshpk@1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} hasBin: true - ssri@10.0.4: - resolution: {integrity: sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ssri@13.0.0: + resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==} + engines: {node: ^20.17.0 || >=22.9.0} stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -10237,13 +10356,8 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - start-server-and-test@2.0.10: - resolution: {integrity: sha512-nZphcfcqGqwk74lbZkqSwClkYz+M5ZPGOMgWxNVJrdztPKN96qe6HooRu6L3TpwITn0lKJJdKACqHbJtqythOQ==} - engines: {node: '>=16'} - hasBin: true - - start-server-and-test@2.0.11: - resolution: {integrity: sha512-TN39gLzPhHAflxyOkE/oMfQGj+pj3JgF6qVicFH/JrXt7xXktidKXwqfRga+ve7lVA8+RgPZVc25VrEPRScaDw==} + start-server-and-test@2.1.3: + resolution: {integrity: sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ==} engines: {node: '>=16'} hasBin: true @@ -10251,11 +10365,15 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} - stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640: @@ -10277,8 +10395,8 @@ packages: react-dom: optional: true - storybook@8.6.12: - resolution: {integrity: sha512-Z/nWYEHBTLK1ZBtAWdhxC0l5zf7ioJ7G4+zYqtTdYeb67gTnxNj80gehf8o8QY9L2zA2+eyMRGLC2V5fI7Z3Tw==} + storybook@10.1.10: + resolution: {integrity: sha512-oK0t0jEogiKKfv5Z1ao4Of99+xWw1TMUGuGRYDQS4kp2yyBsJQEgu7NI7OLYsCDI6gzt5p3RPtl1lqdeVLUi8A==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -10299,8 +10417,8 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - streamx@2.15.0: - resolution: {integrity: sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} strict-event-emitter-types@2.0.0: resolution: {integrity: sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==} @@ -10308,8 +10426,8 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} string-length@4.0.2: @@ -10324,23 +10442,18 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.trim@1.2.7: - resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - - string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} - string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} @@ -10361,8 +10474,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -10392,30 +10505,38 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} - strip-indent@4.0.0: - resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} - strtok3@9.0.1: - resolution: {integrity: sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==} - engines: {node: '>=16'} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} - stylehacks@7.0.4: - resolution: {integrity: sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==} + stylehacks@7.0.6: + resolution: {integrity: sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.4.32 + + superagent@10.2.3: + resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + engines: {node: '>=14.18.0'} + + supertest@7.1.4: + resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} + engines: {node: '>=14.18.0'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} @@ -10429,10 +10550,6 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - supports-color@9.4.0: - resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} - engines: {node: '>=12'} - supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} engines: {node: '>=8'} @@ -10441,29 +10558,23 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svgo@3.3.2: - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} - engines: {node: '>=14.0.0'} + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} hasBin: true symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - systeminformation@5.25.11: - resolution: {integrity: sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==} + systeminformation@5.28.1: + resolution: {integrity: sha512-E1R8ILjak1wPx9CNiQbPV+AeDkT2n5UTOumB8nJ7TPk9QVjhOZvs+xGnp5LqPyQCmh8izuJPZ777COK1X2Cj4g==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true - tar-fs@2.1.2: - resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar-stream@3.1.6: - resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -10472,16 +10583,16 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + tar@7.5.2: + resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} taskkill@5.0.0: resolution: {integrity: sha512-+HRtZ40Vc+6YfCDWCeAsixwxJgMbPY4HHuTgzPYH3JXvqHWUlsCfy+ylXlAKhFNcuLp4xVeWeFBUhDk+7KYUvQ==} engines: {node: '>=14.16'} - terser@5.39.0: - resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} hasBin: true @@ -10489,32 +10600,24 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} textarea-caret@3.1.0: resolution: {integrity: sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - three@0.175.0: - resolution: {integrity: sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==} + three@0.182.0: + resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} throttle-debounce@5.0.2: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} - throttleit@1.0.0: - resolution: {integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==} + throttleit@1.0.1: + resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -10528,17 +10631,14 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} - tinyglobby@0.2.12: - resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} - engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} @@ -10547,31 +10647,46 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tldts-core@6.1.61: - resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} - tldts@6.1.61: - resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts-core@7.0.18: + resolution: {integrity: sha512-jqJC13oP4FFAahv4JT/0WTDrCF9Okv7lpKtOZUGPLiAnNbACcSg8Y8T+Z9xthOmRBqi/Sob4yi0TE0miRCvF7Q==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true - tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + tldts@7.0.18: + resolution: {integrity: sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==} + hasBin: true + + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + to-data-view@1.1.0: resolution: {integrity: sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -10587,28 +10702,36 @@ packages: token-stream@1.0.0: resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} - token-types@6.0.0: - resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} - touch@3.1.0: - resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - tough-cookie@5.0.0: - resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tr46@5.0.0: - resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} - engines: {node: '>=18'} + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} @@ -10630,8 +10753,8 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-api-utils@2.0.1: - resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -10670,8 +10793,8 @@ packages: ts-map@1.0.3: resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==} - tsc-alias@1.8.15: - resolution: {integrity: sha512-yKLVx8ddUurRwhVcS6JFF2ZjksOX2ZWDRIdgt+PQhJBDegIdAdilptiHsuAbx9UFxa16GFrxeKQ2kTcGvR6fkQ==} + tsc-alias@1.8.16: + resolution: {integrity: sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==} engines: {node: '>=16.20.2'} hasBin: true @@ -10682,22 +10805,26 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - tsd@0.31.2: - resolution: {integrity: sha512-VplBAQwvYrHzVihtzXiUVXu5bGcr7uH1juQZ1lmKgkuGNGT+FechUCqmx9/zk7wibcqR2xaNEwCkDyKh+VVZnQ==} + tsd@0.33.0: + resolution: {integrity: sha512-/PQtykJFVw90QICG7zyPDMIyueOXKL7jOJVoX5pILnb3Ux+7QqynOxfVvarE+K+yi7BZyOSY4r+OZNWSWRiEwQ==} engines: {node: '>=14.16'} hasBin: true - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.19.3: - resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -10712,6 +10839,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -10732,67 +10863,58 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.26.1: - resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-fest@5.2.0: + resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} - - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm@0.3.22: - resolution: {integrity: sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==} + typeorm@0.3.28: + resolution: {integrity: sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==} engines: {node: '>=16.13.0'} hasBin: true peerDependencies: - '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 - '@sap/hana-client': ^2.12.25 - better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - hdb-pool: ^0.1.6 + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 ioredis: ^5.0.4 mongodb: ^5.8.0 || ^6.0.0 - mssql: ^9.1.1 || ^10.0.1 || ^11.0.1 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 mysql2: ^2.2.5 || ^3.0.1 oracledb: ^6.3.0 pg: ^8.5.1 pg-native: ^3.0.0 pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 - reflect-metadata: ^0.1.14 || ^0.2.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 sql.js: ^1.4.0 sqlite3: ^5.0.3 ts-node: ^10.7.0 @@ -10804,8 +10926,6 @@ packages: optional: true better-sqlite3: optional: true - hdb-pool: - optional: true ioredis: optional: true mongodb: @@ -10843,8 +10963,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -10855,16 +10975,17 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} - uint8array-extras@1.4.0: - resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} - ulid@2.4.0: - resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==} + ulid@3.0.2: + resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} hasBin: true - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} @@ -10875,37 +10996,37 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@5.28.5: - resolution: {integrity: sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==} - engines: {node: '>=14.0'} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@6.19.8: - resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + undici@6.22.0: + resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} engines: {node: '>=18.17'} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} - unified@11.0.4: - resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unique-filename@3.0.0: - resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + unique-filename@5.0.0: + resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} + engines: {node: ^20.17.0 || >=22.9.0} - unique-slug@4.0.0: - resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + unique-slug@6.0.0: + resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==} + engines: {node: ^20.17.0 || >=22.9.0} - unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -10913,8 +11034,8 @@ packages: unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} @@ -10927,8 +11048,8 @@ packages: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} - universalify@2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} unload@2.4.1: @@ -10938,15 +11059,23 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unplugin@1.4.0: - resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==} + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + + unplugin@2.3.10: + resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} + engines: {node: '>=18.12.0'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -10957,16 +11086,18 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - utf-8-validate@6.0.5: - resolution: {integrity: sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utf-8-validate@6.0.6: + resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} engines: {node: '>=6.14.2'} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -10975,6 +11106,10 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -10992,8 +11127,8 @@ packages: '@vue/composition-api': optional: true - v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} valid-data-url@3.0.1: @@ -11011,33 +11146,41 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@3.1.1: - resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true + vite-plugin-glsl@1.5.5: + resolution: {integrity: sha512-6NM2P4JkM+1hNSqMhM4eagX03bmhEoTyrOrk68y3Q6KXfdF73QIuCb6BmRZvwLPgXTCOBM3Zc8gL1WxctYnrUQ==} + engines: {node: '>= 20.17.0', npm: '>= 10.8.3'} + peerDependencies: + '@rollup/pluginutils': ^5.x + esbuild: '>= 0.25' + vite: '>= 3.x' + peerDependenciesMeta: + '@rollup/pluginutils': + optional: true + esbuild: + optional: true vite-plugin-turbosnap@1.0.3: resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} - vite@6.3.1: - resolution: {integrity: sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -11071,26 +11214,37 @@ packages: peerDependencies: vitest: '>=2.0.0' - vitest@3.1.1: - resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest-websocket-mock@0.5.0: + resolution: {integrity: sha512-vzBWeuF/kD/OCOFzB7WAclb7PxfI105qPkZtdOkPMwZdilBskQjJL4l319JtPtmeovDU7ZVhO3hTfGPjM4txQQ==} + peerDependencies: + vitest: '>=3' + + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.1.1 - '@vitest/ui': 3.1.1 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -11117,28 +11271,28 @@ packages: vscode-languageserver-types@3.17.5: resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-component-meta@2.0.16: - resolution: {integrity: sha512-IyIMClUMYcKxAL34GqdPbR4V45MUeHXqQiZlHxeYMV5Qcqp4M+CEmtGpF//XBSS138heDkYkceHAtJQjLUB1Lw==} + vue-component-meta@2.2.12: + resolution: {integrity: sha512-dQU6/obNSNbennJ1xd+rhDid4g3vQro+9qUBBIg8HMZH2Zs1jTpkFNxuQ3z77bOlU+ew08Qck9sbYkdSePr0Pw==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - vue-component-type-helpers@1.8.4: - resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==} + vue-component-type-helpers@2.2.12: + resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} - vue-component-type-helpers@2.0.16: - resolution: {integrity: sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==} + vue-component-type-helpers@3.2.1: + resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==} - vue-component-type-helpers@2.2.8: - resolution: {integrity: sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==} + vue-component-type-helpers@3.2.2: + resolution: {integrity: sha512-x8C2nx5XlUNM0WirgfTkHjJGO/ABBxlANZDtHw2HclHtQnn+RFPTnbjMJn8jHZW4TlUam0asHcA14lf1C6Jb+A==} - vue-demi@0.14.7: - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -11148,13 +11302,13 @@ packages: '@vue/composition-api': optional: true - vue-docgen-api@4.75.1: - resolution: {integrity: sha512-MECZ3uExz+ssmhD/2XrFoQQs93y17IVO1KDYTp8nr6i9GNrk67AAto6QAtilW1H/pTDPMkQxJ7w/25ZIqVtfAA==} + vue-docgen-api@4.79.2: + resolution: {integrity: sha512-n9ENAcs+40awPZMsas7STqjkZiVlIjxIKgiJr5rSohDP0/JCrD9VtlzNojafsA1MChm/hz2h3PDtUedx3lbgfA==} peerDependencies: vue: '>=2' - vue-eslint-parser@10.1.3: - resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==} + vue-eslint-parser@10.2.0: + resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -11164,40 +11318,31 @@ packages: peerDependencies: vue: '>=2' - vue-template-compiler@2.7.14: - resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} - - vue-tsc@2.2.8: - resolution: {integrity: sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ==} + vue-tsc@3.2.1: + resolution: {integrity: sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.13: - resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - vuedraggable@4.1.0: - resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} - peerDependencies: - vue: ^3.0.1 + w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wait-on@8.0.2: - resolution: {integrity: sha512-qHlU6AawrgAIHlueGQHQ+ETcPLAauXbnoTKl3RKq20W0T8x0DKVAo5xWIYjHSyvHxQlcYbFdR0jp4T9bDVITFA==} - engines: {node: '>=12.0.0'} - hasBin: true - - wait-on@8.0.3: - resolution: {integrity: sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==} - engines: {node: '>=12.0.0'} + wait-on@9.0.3: + resolution: {integrity: sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==} + engines: {node: '>=20.0.0'} hasBin: true walker@1.0.8: @@ -11207,6 +11352,10 @@ packages: resolution: {integrity: sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==} engines: {node: '>=12'} + wawoff2@2.0.1: + resolution: {integrity: sha512-r0CEmvpH63r4T15ebFqeOjGqU4+EgTx4I510NtK35EMciSdcTxCw3Byy3JnBonz7iyIFZ0AbVo0bbFpEVuhCYA==} + hasBin: true + web-push@3.6.7: resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==} engines: {node: '>= 16'} @@ -11216,12 +11365,8 @@ packages: resolution: {integrity: sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==} engines: {node: '>=10.0.0'} - web-streams-polyfill@3.2.1: - resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} - engines: {node: '>= 8'} - - web-streams-polyfill@4.0.0: - resolution: {integrity: sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} webidl-conversions@3.0.1: @@ -11231,16 +11376,22 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} - webpack-virtual-modules@0.5.0: - resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} @@ -11250,28 +11401,34 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} - whatwg-url@14.1.0: - resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} - engines: {node: '>=18'} + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - - which-collection@1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - - which-module@2.0.0: - resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} - - which-typed-array@1.1.11: - resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} which@1.3.1: @@ -11283,9 +11440,9 @@ packages: engines: {node: '>= 8'} hasBin: true - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true why-is-node-running@2.3.0: @@ -11316,6 +11473,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -11323,8 +11484,8 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -11335,6 +11496,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xev@3.0.2: resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==} @@ -11382,6 +11547,9 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -11394,6 +11562,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -11406,6 +11578,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} @@ -11417,12 +11593,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} - yoctocolors@2.1.1: - resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} zip-stream@6.0.1: @@ -11434,78 +11610,102 @@ packages: snapshots: - '@adobe/css-tools@4.4.0': {} + '@acemir/cssom@0.9.30': + optional: true - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@adobe/css-tools@4.4.4': {} - '@analytics/cookie-utils@0.2.12': + '@analytics/cookie-utils@0.2.14': dependencies: - '@analytics/global-storage-utils': 0.1.7 + '@analytics/global-storage-utils': 0.1.9 - '@analytics/core@0.12.17(@types/dlv@1.1.5)': + '@analytics/core@0.13.2(@types/dlv@1.1.5)': dependencies: - '@analytics/global-storage-utils': 0.1.7 - '@analytics/type-utils': 0.6.2 - analytics-utils: 1.0.14(@types/dlv@1.1.5) + '@analytics/global-storage-utils': 0.1.9 + '@analytics/type-utils': 0.6.4 + analytics-utils: 1.1.1(@types/dlv@1.1.5) transitivePeerDependencies: - '@types/dlv' - '@analytics/global-storage-utils@0.1.7': + '@analytics/global-storage-utils@0.1.9': dependencies: - '@analytics/type-utils': 0.6.2 + '@analytics/type-utils': 0.6.4 '@analytics/google-analytics@1.1.0': {} - '@analytics/localstorage-utils@0.1.10': + '@analytics/localstorage-utils@0.1.12': dependencies: - '@analytics/global-storage-utils': 0.1.7 + '@analytics/global-storage-utils': 0.1.9 - '@analytics/session-storage-utils@0.0.7': + '@analytics/session-storage-utils@0.0.9': dependencies: - '@analytics/global-storage-utils': 0.1.7 + '@analytics/global-storage-utils': 0.1.9 - '@analytics/storage-utils@0.4.2': + '@analytics/storage-utils@0.4.4': dependencies: - '@analytics/cookie-utils': 0.2.12 - '@analytics/global-storage-utils': 0.1.7 - '@analytics/localstorage-utils': 0.1.10 - '@analytics/session-storage-utils': 0.0.7 - '@analytics/type-utils': 0.6.2 + '@analytics/cookie-utils': 0.2.14 + '@analytics/global-storage-utils': 0.1.9 + '@analytics/localstorage-utils': 0.1.12 + '@analytics/session-storage-utils': 0.0.9 + '@analytics/type-utils': 0.6.4 - '@analytics/type-utils@0.6.2': {} + '@analytics/type-utils@0.6.4': {} - '@apidevtools/swagger-methods@3.0.2': {} - - '@asamuzakjp/css-color@2.8.3': + '@apidevtools/json-schema-ref-parser@14.2.1(@types/json-schema@7.0.15)': 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) - '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 - lru-cache: 10.4.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + + '@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.2) + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + '@asamuzakjp/css-color@4.1.1': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.4 + optional: true + + '@asamuzakjp/dom-selector@6.7.6': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.4 + optional: true + + '@asamuzakjp/nwsapi@2.3.9': + optional: true '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.957.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.957.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-locate-window': 3.208.0 - '@smithy/util-utf8': 2.0.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-crypto/sha256-browser@5.2.0': @@ -11513,15 +11713,15 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-locate-window': 3.208.0 - '@smithy/util-utf8': 2.0.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.775.0 + '@aws-sdk/types': 3.957.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -11530,905 +11730,1127 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/util-utf8': 2.0.0 + '@aws-sdk/types': 3.957.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.782.0': + '@aws-sdk/client-s3@3.958.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/credential-provider-node': 3.782.0 - '@aws-sdk/middleware-bucket-endpoint': 3.775.0 - '@aws-sdk/middleware-expect-continue': 3.775.0 - '@aws-sdk/middleware-flexible-checksums': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-location-constraint': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-sdk-s3': 3.775.0 - '@aws-sdk/middleware-ssec': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/signature-v4-multi-region': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@aws-sdk/xml-builder': 3.775.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/eventstream-serde-browser': 4.0.2 - '@smithy/eventstream-serde-config-resolver': 4.1.0 - '@smithy/eventstream-serde-node': 4.0.2 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-blob-browser': 4.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/hash-stream-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/md5-js': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 - '@smithy/util-waiter': 4.0.3 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/credential-provider-node': 3.958.0 + '@aws-sdk/middleware-bucket-endpoint': 3.957.0 + '@aws-sdk/middleware-expect-continue': 3.957.0 + '@aws-sdk/middleware-flexible-checksums': 3.957.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-location-constraint': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-sdk-s3': 3.957.0 + '@aws-sdk/middleware-ssec': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/signature-v4-multi-region': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/eventstream-serde-browser': 4.2.7 + '@smithy/eventstream-serde-config-resolver': 4.3.7 + '@smithy/eventstream-serde-node': 4.2.7 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-blob-browser': 4.2.8 + '@smithy/hash-node': 4.2.7 + '@smithy/hash-stream-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/md5-js': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.7 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.782.0': + '@aws-sdk/client-sesv2@3.938.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.936.0 + '@aws-sdk/credential-provider-node': 3.936.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.936.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/signature-v4-multi-region': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.936.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.775.0': + '@aws-sdk/client-sso@3.936.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/core': 3.2.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - fast-xml-parser: 4.4.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.775.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.775.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/property-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-stream': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.782.0': - dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/credential-provider-env': 3.775.0 - '@aws-sdk/credential-provider-http': 3.775.0 - '@aws-sdk/credential-provider-process': 3.775.0 - '@aws-sdk/credential-provider-sso': 3.782.0 - '@aws-sdk/credential-provider-web-identity': 3.782.0 - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.936.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.936.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.936.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.782.0': + '@aws-sdk/client-sso@3.958.0': dependencies: - '@aws-sdk/credential-provider-env': 3.775.0 - '@aws-sdk/credential-provider-http': 3.775.0 - '@aws-sdk/credential-provider-ini': 3.782.0 - '@aws-sdk/credential-provider-process': 3.775.0 - '@aws-sdk/credential-provider-sso': 3.782.0 - '@aws-sdk/credential-provider-web-identity': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.775.0': + '@aws-sdk/core@3.936.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/xml-builder': 3.930.0 + '@smithy/core': 3.20.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.782.0': + '@aws-sdk/core@3.957.0': dependencies: - '@aws-sdk/client-sso': 3.782.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/token-providers': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/xml-builder': 3.957.0 + '@smithy/core': 3.20.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.957.0': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/credential-provider-env': 3.936.0 + '@aws-sdk/credential-provider-http': 3.936.0 + '@aws-sdk/credential-provider-login': 3.936.0 + '@aws-sdk/credential-provider-process': 3.936.0 + '@aws-sdk/credential-provider-sso': 3.936.0 + '@aws-sdk/credential-provider-web-identity': 3.936.0 + '@aws-sdk/nested-clients': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.782.0': + '@aws-sdk/credential-provider-ini@3.958.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/credential-provider-env': 3.957.0 + '@aws-sdk/credential-provider-http': 3.957.0 + '@aws-sdk/credential-provider-login': 3.958.0 + '@aws-sdk/credential-provider-process': 3.957.0 + '@aws-sdk/credential-provider-sso': 3.958.0 + '@aws-sdk/credential-provider-web-identity': 3.958.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/lib-storage@3.782.0(@aws-sdk/client-s3@3.782.0)': + '@aws-sdk/credential-provider-login@3.936.0': dependencies: - '@aws-sdk/client-s3': 3.782.0 - '@smithy/abort-controller': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/smithy-client': 4.2.0 + '@aws-sdk/core': 3.936.0 + '@aws-sdk/nested-clients': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.936.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.936.0 + '@aws-sdk/credential-provider-http': 3.936.0 + '@aws-sdk/credential-provider-ini': 3.936.0 + '@aws-sdk/credential-provider-process': 3.936.0 + '@aws-sdk/credential-provider-sso': 3.936.0 + '@aws-sdk/credential-provider-web-identity': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.958.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.957.0 + '@aws-sdk/credential-provider-http': 3.957.0 + '@aws-sdk/credential-provider-ini': 3.958.0 + '@aws-sdk/credential-provider-process': 3.957.0 + '@aws-sdk/credential-provider-sso': 3.958.0 + '@aws-sdk/credential-provider-web-identity': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.936.0': + dependencies: + '@aws-sdk/client-sso': 3.936.0 + '@aws-sdk/core': 3.936.0 + '@aws-sdk/token-providers': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-sso@3.958.0': + dependencies: + '@aws-sdk/client-sso': 3.958.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/token-providers': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/nested-clients': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/lib-storage@3.958.0(@aws-sdk/client-s3@3.958.0)': + dependencies: + '@aws-sdk/client-s3': 3.958.0 + '@smithy/abort-controller': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/smithy-client': 4.10.2 buffer: 5.6.0 events: 3.3.0 stream-browserify: 3.0.0 tslib: 2.8.1 - '@aws-sdk/middleware-bucket-endpoint@3.775.0': + '@aws-sdk/middleware-bucket-endpoint@3.957.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-arn-parser': 3.723.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-arn-parser': 3.957.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-config-provider': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-expect-continue@3.775.0': + '@aws-sdk/middleware-expect-continue@3.957.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.775.0': + '@aws-sdk/middleware-flexible-checksums@3.957.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/is-array-buffer': 4.0.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/crc64-nvme': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.775.0': + '@aws-sdk/middleware-host-header@3.936.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-location-constraint@3.775.0': + '@aws-sdk/middleware-host-header@3.957.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.775.0': + '@aws-sdk/middleware-location-constraint@3.957.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.775.0': + '@aws-sdk/middleware-logger@3.936.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.775.0': + '@aws-sdk/middleware-logger@3.957.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-arn-parser': 3.723.0 - '@smithy/core': 3.2.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.775.0': + '@aws-sdk/middleware-recursion-detection@3.936.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.936.0 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.782.0': + '@aws-sdk/middleware-recursion-detection@3.957.0': dependencies: - '@aws-sdk/core': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@smithy/core': 3.2.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.782.0': + '@aws-sdk/middleware-sdk-s3@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.20.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-arn-parser': 3.957.0 + '@smithy/core': 3.20.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@smithy/core': 3.20.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@smithy/core': 3.20.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.936.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.775.0 - '@aws-sdk/middleware-host-header': 3.775.0 - '@aws-sdk/middleware-logger': 3.775.0 - '@aws-sdk/middleware-recursion-detection': 3.775.0 - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/region-config-resolver': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@aws-sdk/util-endpoints': 3.782.0 - '@aws-sdk/util-user-agent-browser': 3.775.0 - '@aws-sdk/util-user-agent-node': 3.782.0 - '@smithy/config-resolver': 4.1.0 - '@smithy/core': 3.2.0 - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/hash-node': 4.0.2 - '@smithy/invalid-dependency': 4.0.2 - '@smithy/middleware-content-length': 4.0.2 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-retry': 4.1.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/middleware-stack': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/protocol-http': 5.1.0 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.8 - '@smithy/util-defaults-mode-node': 4.0.8 - '@smithy/util-endpoints': 3.0.2 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - '@smithy/util-utf8': 4.0.0 + '@aws-sdk/core': 3.936.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.936.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.936.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.775.0': + '@aws-sdk/nested-clients@3.958.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.775.0': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.775.0 - '@aws-sdk/types': 3.775.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/signature-v4': 5.0.2 - '@smithy/types': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.782.0': - dependencies: - '@aws-sdk/nested-clients': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.775.0': + '@aws-sdk/region-config-resolver@3.936.0': dependencies: - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.936.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.723.0': + '@aws-sdk/region-config-resolver@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.936.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.957.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.936.0': + dependencies: + '@aws-sdk/core': 3.936.0 + '@aws-sdk/nested-clients': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/token-providers@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.936.0': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.957.0': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.782.0': - dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 - '@smithy/util-endpoints': 3.0.2 - tslib: 2.8.1 - - '@aws-sdk/util-locate-window@3.208.0': + '@aws-sdk/util-arn-parser@3.957.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.775.0': + '@aws-sdk/util-endpoints@3.936.0': dependencies: - '@aws-sdk/types': 3.775.0 - '@smithy/types': 4.2.0 - bowser: 2.11.0 + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-endpoints': 3.2.7 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.782.0': + '@aws-sdk/util-endpoints@3.957.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.782.0 - '@aws-sdk/types': 3.775.0 - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-endpoints': 3.2.7 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.775.0': + '@aws-sdk/util-locate-window@3.893.0': dependencies: - '@smithy/types': 4.2.0 tslib: 2.8.1 - '@babel/code-frame@7.24.7': + '@aws-sdk/util-user-agent-browser@3.936.0': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.1 + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.11.0 + bowser: 2.12.1 + tslib: 2.8.1 - '@babel/compat-data@7.24.7': {} - - '@babel/core@7.23.5': + '@aws-sdk/util-user-agent-browser@3.957.0': dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.23.5 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.5) - '@babel/helpers': 7.23.5 - '@babel/parser': 7.25.6 - '@babel/template': 7.22.15 - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + bowser: 2.12.1 + tslib: 2.8.1 - '@babel/core@7.24.7': + '@aws-sdk/util-user-agent-node@3.936.0': dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@aws-sdk/middleware-user-agent': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 - '@babel/generator@7.23.5': + '@aws-sdk/util-user-agent-node@3.957.0': dependencies: - '@babel/types': 7.25.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 - '@babel/generator@7.24.7': + '@aws-sdk/xml-builder@3.930.0': dependencies: - '@babel/types': 7.25.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + '@smithy/types': 4.11.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 - '@babel/helper-compilation-targets@7.22.15': + '@aws-sdk/xml-builder@3.957.0': dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 + '@smithy/types': 4.11.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 - '@babel/helper-compilation-targets@7.24.7': + '@aws/lambda-invoke-store@0.2.2': {} + + '@babel/code-frame@7.27.1': dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-environment-visitor@7.24.7': - dependencies: - '@babel/types': 7.25.6 - - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.25.6 - - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.25.6 - - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.22.5': {} - - '@babel/helper-simple-access@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-split-export-declaration@7.24.7': - dependencies: - '@babel/types': 7.25.6 - - '@babel/helper-string-parser@7.24.8': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-option@7.24.7': {} - - '@babel/helpers@7.23.5': - dependencies: - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - - '@babel/helpers@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.25.6 - - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/parser@7.25.6': - dependencies: - '@babel/types': 7.25.6 + '@babel/compat-data@7.28.5': {} - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)': + '@babel/core@7.28.5': dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)': - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/runtime@7.23.4': - dependencies: - regenerator-runtime: 0.14.0 - - '@babel/runtime@7.27.0': - dependencies: - regenerator-runtime: 0.14.0 - - '@babel/template@7.22.15': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - - '@babel/template@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - - '@babel/traverse@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - debug: 4.4.0(supports-color@5.5.0) - globals: 11.12.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@10.2.2) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/types@7.25.6': + '@babel/generator@7.28.5': dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bcoe/v8-coverage@0.2.3': {} '@bcoe/v8-coverage@1.0.2': {} - '@bundled-es-modules/cookie@2.0.1': - dependencies: - cookie: 0.7.2 + '@borewit/text-codec@0.1.1': {} - '@bundled-es-modules/statuses@1.0.1': - dependencies: - statuses: 2.0.1 - - '@bundled-es-modules/tough-cookie@0.1.6': - dependencies: - '@types/tough-cookie': 4.0.5 - tough-cookie: 4.1.4 - - '@canvas/image-data@1.0.0': {} + '@canvas/image-data@1.1.0': {} '@chainsafe/is-ip@2.1.0': {} - '@colors/colors@1.5.0': + '@cropper/element-canvas@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-crosshair@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-grid@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-handle@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-image@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/element-canvas': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-selection@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/element-canvas': 2.1.0 + '@cropper/element-image': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-shade@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/element-canvas': 2.1.0 + '@cropper/element-selection': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element-viewer@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/element-canvas': 2.1.0 + '@cropper/element-image': 2.1.0 + '@cropper/element-selection': 2.1.0 + '@cropper/utils': 2.1.0 + + '@cropper/element@2.1.0': + dependencies: + '@cropper/utils': 2.1.0 + + '@cropper/elements@2.1.0': + dependencies: + '@cropper/element': 2.1.0 + '@cropper/element-canvas': 2.1.0 + '@cropper/element-crosshair': 2.1.0 + '@cropper/element-grid': 2.1.0 + '@cropper/element-handle': 2.1.0 + '@cropper/element-image': 2.1.0 + '@cropper/element-selection': 2.1.0 + '@cropper/element-shade': 2.1.0 + '@cropper/element-viewer': 2.1.0 + + '@cropper/utils@2.1.0': {} + + '@csstools/color-helpers@5.1.0': optional: true - '@cropper/element-canvas@2.0.0': + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@cropper/element': 2.0.0 - '@cropper/utils': 2.0.0 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true - '@cropper/element-crosshair@2.0.0': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@cropper/element': 2.0.0 - '@cropper/utils': 2.0.0 + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true - '@cropper/element-grid@2.0.0': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: - '@cropper/element': 2.0.0 - '@cropper/utils': 2.0.0 + '@csstools/css-tokenizer': 3.0.4 + optional: true - '@cropper/element-handle@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/utils': 2.0.0 + '@csstools/css-syntax-patches-for-csstree@1.0.22': + optional: true - '@cropper/element-image@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/element-canvas': 2.0.0 - '@cropper/utils': 2.0.0 + '@csstools/css-tokenizer@3.0.4': + optional: true - '@cropper/element-selection@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/element-canvas': 2.0.0 - '@cropper/element-image': 2.0.0 - '@cropper/utils': 2.0.0 - - '@cropper/element-shade@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/element-canvas': 2.0.0 - '@cropper/element-selection': 2.0.0 - '@cropper/utils': 2.0.0 - - '@cropper/element-viewer@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/element-canvas': 2.0.0 - '@cropper/element-image': 2.0.0 - '@cropper/element-selection': 2.0.0 - '@cropper/utils': 2.0.0 - - '@cropper/element@2.0.0': - dependencies: - '@cropper/utils': 2.0.0 - - '@cropper/elements@2.0.0': - dependencies: - '@cropper/element': 2.0.0 - '@cropper/element-canvas': 2.0.0 - '@cropper/element-crosshair': 2.0.0 - '@cropper/element-grid': 2.0.0 - '@cropper/element-handle': 2.0.0 - '@cropper/element-image': 2.0.0 - '@cropper/element-selection': 2.0.0 - '@cropper/element-shade': 2.0.0 - '@cropper/element-viewer': 2.0.0 - - '@cropper/utils@2.0.0': {} - - '@csstools/color-helpers@5.0.1': {} - - '@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)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 - - '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': - dependencies: - '@csstools/color-helpers': 5.0.1 - '@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) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 - - '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': - dependencies: - '@csstools/css-tokenizer': 3.0.3 - - '@csstools/css-tokenizer@3.0.3': {} - - '@cypress/request@3.0.7': + '@cypress/request@3.0.9': dependencies: aws-sign2: 0.7.0 - aws4: 1.12.0 + aws4: 1.13.2 caseless: 0.12.0 combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.2 - http-signature: 1.4.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.13.1 - safe-buffer: 5.2.1 - tough-cookie: 5.0.0 - tunnel-agent: 0.6.0 - uuid: 8.3.2 - - '@cypress/request@3.0.8': - dependencies: - aws-sign2: 0.7.0 - aws4: 1.12.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 4.0.2 + form-data: 4.0.5 http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 @@ -12437,7 +12859,7 @@ snapshots: performance-now: 2.1.0 qs: 6.14.0 safe-buffer: 5.2.1 - tough-cookie: 5.0.0 + tough-cookie: 5.1.2 tunnel-agent: 0.6.0 uuid: 8.3.2 @@ -12448,373 +12870,310 @@ snapshots: transitivePeerDependencies: - supports-color - '@digitalbazaar/http-client@3.4.1(web-streams-polyfill@4.0.0)': + '@digitalbazaar/http-client@4.2.0': dependencies: - ky: 0.33.3 - ky-universal: 0.11.0(ky@0.33.3)(web-streams-polyfill@4.0.0) - undici: 5.28.5 - transitivePeerDependencies: - - web-streams-polyfill + ky: 1.14.0 + undici: 6.22.0 - '@discordapp/twemoji@15.1.0': + '@discordapp/twemoji@16.0.1': dependencies: - '@twemoji/parser': 15.1.0 + '@twemoji/parser': 16.0.0 fs-extra: 8.1.0 jsonfile: 5.0.0 universalify: 0.1.2 - '@emnapi/runtime@1.4.3': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.19.11': optional: true - '@esbuild/aix-ppc64@0.25.0': - optional: true - - '@esbuild/aix-ppc64@0.25.2': + '@esbuild/aix-ppc64@0.27.2': optional: true '@esbuild/android-arm64@0.19.11': optional: true - '@esbuild/android-arm64@0.25.0': - optional: true - - '@esbuild/android-arm64@0.25.2': + '@esbuild/android-arm64@0.27.2': optional: true '@esbuild/android-arm@0.19.11': optional: true - '@esbuild/android-arm@0.25.0': - optional: true - - '@esbuild/android-arm@0.25.2': + '@esbuild/android-arm@0.27.2': optional: true '@esbuild/android-x64@0.19.11': optional: true - '@esbuild/android-x64@0.25.0': - optional: true - - '@esbuild/android-x64@0.25.2': + '@esbuild/android-x64@0.27.2': optional: true '@esbuild/darwin-arm64@0.19.11': optional: true - '@esbuild/darwin-arm64@0.25.0': - optional: true - - '@esbuild/darwin-arm64@0.25.2': + '@esbuild/darwin-arm64@0.27.2': optional: true '@esbuild/darwin-x64@0.19.11': optional: true - '@esbuild/darwin-x64@0.25.0': - optional: true - - '@esbuild/darwin-x64@0.25.2': + '@esbuild/darwin-x64@0.27.2': optional: true '@esbuild/freebsd-arm64@0.19.11': optional: true - '@esbuild/freebsd-arm64@0.25.0': - optional: true - - '@esbuild/freebsd-arm64@0.25.2': + '@esbuild/freebsd-arm64@0.27.2': optional: true '@esbuild/freebsd-x64@0.19.11': optional: true - '@esbuild/freebsd-x64@0.25.0': - optional: true - - '@esbuild/freebsd-x64@0.25.2': + '@esbuild/freebsd-x64@0.27.2': optional: true '@esbuild/linux-arm64@0.19.11': optional: true - '@esbuild/linux-arm64@0.25.0': - optional: true - - '@esbuild/linux-arm64@0.25.2': + '@esbuild/linux-arm64@0.27.2': optional: true '@esbuild/linux-arm@0.19.11': optional: true - '@esbuild/linux-arm@0.25.0': - optional: true - - '@esbuild/linux-arm@0.25.2': + '@esbuild/linux-arm@0.27.2': optional: true '@esbuild/linux-ia32@0.19.11': optional: true - '@esbuild/linux-ia32@0.25.0': - optional: true - - '@esbuild/linux-ia32@0.25.2': + '@esbuild/linux-ia32@0.27.2': optional: true '@esbuild/linux-loong64@0.19.11': optional: true - '@esbuild/linux-loong64@0.25.0': - optional: true - - '@esbuild/linux-loong64@0.25.2': + '@esbuild/linux-loong64@0.27.2': optional: true '@esbuild/linux-mips64el@0.19.11': optional: true - '@esbuild/linux-mips64el@0.25.0': - optional: true - - '@esbuild/linux-mips64el@0.25.2': + '@esbuild/linux-mips64el@0.27.2': optional: true '@esbuild/linux-ppc64@0.19.11': optional: true - '@esbuild/linux-ppc64@0.25.0': - optional: true - - '@esbuild/linux-ppc64@0.25.2': + '@esbuild/linux-ppc64@0.27.2': optional: true '@esbuild/linux-riscv64@0.19.11': optional: true - '@esbuild/linux-riscv64@0.25.0': - optional: true - - '@esbuild/linux-riscv64@0.25.2': + '@esbuild/linux-riscv64@0.27.2': optional: true '@esbuild/linux-s390x@0.19.11': optional: true - '@esbuild/linux-s390x@0.25.0': - optional: true - - '@esbuild/linux-s390x@0.25.2': + '@esbuild/linux-s390x@0.27.2': optional: true '@esbuild/linux-x64@0.19.11': optional: true - '@esbuild/linux-x64@0.25.0': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/linux-x64@0.25.2': - optional: true - - '@esbuild/netbsd-arm64@0.25.0': - optional: true - - '@esbuild/netbsd-arm64@0.25.2': + '@esbuild/netbsd-arm64@0.27.2': optional: true '@esbuild/netbsd-x64@0.19.11': optional: true - '@esbuild/netbsd-x64@0.25.0': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.2': - optional: true - - '@esbuild/openbsd-arm64@0.25.0': - optional: true - - '@esbuild/openbsd-arm64@0.25.2': + '@esbuild/openbsd-arm64@0.27.2': optional: true '@esbuild/openbsd-x64@0.19.11': optional: true - '@esbuild/openbsd-x64@0.25.0': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.2': + '@esbuild/openharmony-arm64@0.27.2': optional: true '@esbuild/sunos-x64@0.19.11': optional: true - '@esbuild/sunos-x64@0.25.0': - optional: true - - '@esbuild/sunos-x64@0.25.2': + '@esbuild/sunos-x64@0.27.2': optional: true '@esbuild/win32-arm64@0.19.11': optional: true - '@esbuild/win32-arm64@0.25.0': - optional: true - - '@esbuild/win32-arm64@0.25.2': + '@esbuild/win32-arm64@0.27.2': optional: true '@esbuild/win32-ia32@0.19.11': optional: true - '@esbuild/win32-ia32@0.25.0': - optional: true - - '@esbuild/win32-ia32@0.25.2': + '@esbuild/win32-ia32@0.27.2': optional: true '@esbuild/win32-x64@0.19.11': optional: true - '@esbuild/win32-x64@0.25.0': + '@esbuild/win32-x64@0.27.2': optional: true - '@esbuild/win32-x64@0.25.2': - optional: true - - '@eslint-community/eslint-utils@4.4.0(eslint@9.22.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.39.2)': dependencies: - eslint: 9.22.0 + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.1.1': {} + '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.19.2': + '@eslint/compat@1.4.0(eslint@9.39.2)': dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.0(supports-color@5.5.0) + '@eslint/core': 0.16.0 + optionalDependencies: + eslint: 9.39.2 + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.1.0': {} + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 - '@eslint/core@0.12.0': + '@eslint/core@0.16.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.0': + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@5.5.0) - espree: 10.3.0 + debug: 4.4.3(supports-color@10.2.2) + espree: 10.4.0 globals: 14.0.0 - ignore: 5.3.1 - import-fresh: 3.3.0 - js-yaml: 4.1.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.22.0': {} + '@eslint/js@9.39.2': {} - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.2.7': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 0.12.0 + '@eslint/core': 0.17.0 levn: 0.4.1 - '@fastify/accept-negotiator@2.0.0': {} + '@fastify/accept-negotiator@2.0.1': {} - '@fastify/accepts@5.0.2': + '@fastify/accepts@5.0.4': dependencies: accepts: 1.3.8 - fastify-plugin: 5.0.0 + fastify-plugin: 5.1.0 - '@fastify/ajv-compiler@4.0.0': + '@fastify/ajv-compiler@4.0.5': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) - fast-uri: 3.0.1 + fast-uri: 3.1.0 - '@fastify/busboy@2.1.0': {} + '@fastify/busboy@3.2.0': {} - '@fastify/busboy@3.0.0': {} - - '@fastify/cookie@11.0.2': + '@fastify/cors@11.2.0': dependencies: - cookie: 1.0.1 - fastify-plugin: 5.0.0 + fastify-plugin: 5.1.0 + toad-cache: 3.7.0 - '@fastify/cors@10.1.0': - dependencies: - fastify-plugin: 5.0.0 - mnemonist: 0.40.0 + '@fastify/deepmerge@3.1.0': {} - '@fastify/deepmerge@2.0.0': {} - - '@fastify/error@4.0.0': {} + '@fastify/error@4.2.0': {} '@fastify/express@4.0.2': dependencies: - express: 4.21.1 - fastify-plugin: 5.0.0 + express: 4.21.2 + fastify-plugin: 5.1.0 transitivePeerDependencies: - supports-color - '@fastify/fast-json-stringify-compiler@5.0.0': + '@fastify/fast-json-stringify-compiler@5.0.3': dependencies: - fast-json-stringify: 6.0.0 + fast-json-stringify: 6.1.1 - '@fastify/forwarded@3.0.0': {} + '@fastify/forwarded@3.0.1': {} - '@fastify/http-proxy@10.0.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)': + '@fastify/http-proxy@11.4.1(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: - '@fastify/reply-from': 11.0.0 + '@fastify/reply-from': 12.5.0 fast-querystring: 1.1.2 - fastify-plugin: 5.0.0 - ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + fastify-plugin: 5.1.0 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - utf-8-validate - '@fastify/merge-json-schemas@0.1.1': + '@fastify/merge-json-schemas@0.2.1': dependencies: - fast-deep-equal: 3.1.3 + dequal: 2.0.3 - '@fastify/multipart@9.0.3': + '@fastify/multipart@9.3.0': dependencies: - '@fastify/busboy': 3.0.0 - '@fastify/deepmerge': 2.0.0 - '@fastify/error': 4.0.0 - fastify-plugin: 5.0.0 - secure-json-parse: 3.0.2 + '@fastify/busboy': 3.2.0 + '@fastify/deepmerge': 3.1.0 + '@fastify/error': 4.2.0 + fastify-plugin: 5.1.0 + secure-json-parse: 4.1.0 - '@fastify/proxy-addr@5.0.0': + '@fastify/proxy-addr@5.1.0': dependencies: - '@fastify/forwarded': 3.0.0 - ipaddr.js: 2.2.0 + '@fastify/forwarded': 3.0.1 + ipaddr.js: 2.3.0 - '@fastify/reply-from@11.0.0': + '@fastify/reply-from@12.5.0': dependencies: - '@fastify/error': 4.0.0 - end-of-stream: 1.4.4 - fast-content-type-parse: 2.0.0 + '@fastify/error': 4.2.0 + end-of-stream: 1.4.5 + fast-content-type-parse: 3.0.0 fast-querystring: 1.1.2 - fastify-plugin: 4.5.1 + fastify-plugin: 5.1.0 toad-cache: 3.7.0 - undici: 6.19.8 + undici: 7.16.0 - '@fastify/send@3.3.1': + '@fastify/send@4.1.0': dependencies: '@lukeed/ms': 2.0.2 escape-html: 1.0.3 @@ -12822,170 +13181,187 @@ snapshots: http-errors: 2.0.0 mime: 3.0.0 - '@fastify/static@8.1.1': + '@fastify/static@8.3.0': dependencies: - '@fastify/accept-negotiator': 2.0.0 - '@fastify/send': 3.3.1 + '@fastify/accept-negotiator': 2.0.1 + '@fastify/send': 4.1.0 content-disposition: 0.5.4 - fastify-plugin: 5.0.0 - fastq: 1.17.1 - glob: 11.0.1 + fastify-plugin: 5.1.0 + fastq: 1.19.1 + glob: 11.1.0 - '@fastify/view@10.0.2': + '@file-type/xml@0.4.4': dependencies: - fastify-plugin: 5.0.0 - toad-cache: 3.7.0 + sax: 1.4.3 + strtok3: 10.3.4 '@github/webauthn-json@2.1.1': {} + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + '@hapi/boom@10.0.1': dependencies: - '@hapi/hoek': 11.0.4 + '@hapi/hoek': 11.0.7 '@hapi/bourne@3.0.0': {} - '@hapi/hoek@11.0.4': {} + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} '@hapi/hoek@9.3.0': {} + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.4': {} + '@hapi/topo@5.1.0': dependencies: '@hapi/hoek': 9.3.0 - '@hapi/wreck@18.0.1': + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/wreck@18.1.0': dependencies: '@hapi/boom': 10.0.1 '@hapi/bourne': 3.0.0 - '@hapi/hoek': 11.0.4 + '@hapi/hoek': 11.0.7 - '@hexagon/base64@1.1.27': {} + '@hexagon/base64@1.1.28': {} '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.0 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/momoa@2.0.4': {} - '@humanwhocodes/retry@0.3.0': {} + '@humanwhocodes/retry@0.4.3': {} - '@humanwhocodes/retry@0.4.2': {} - - '@img/sharp-darwin-arm64@0.34.1': + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.34.1': + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.1.0': + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true - '@img/sharp-libvips-darwin-x64@1.1.0': + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm64@1.1.0': + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm@1.1.0': + '@img/sharp-libvips-linux-arm@1.0.5': optional: true - '@img/sharp-libvips-linux-ppc64@1.1.0': + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true - '@img/sharp-libvips-linux-s390x@1.1.0': + '@img/sharp-libvips-linux-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-x64@1.1.0': + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.1.0': - optional: true - - '@img/sharp-linux-arm64@0.34.1': + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm@0.34.1': + '@img/sharp-linux-arm@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-s390x@0.34.1': + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-x64@0.34.1': + '@img/sharp-linux-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.1': + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.1': + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-wasm32@0.34.1': + '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.4.3 + '@emnapi/runtime': 1.7.1 optional: true - '@img/sharp-win32-ia32@0.34.1': + '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-x64@0.34.1': + '@img/sharp-win32-x64@0.33.5': optional: true - '@inquirer/confirm@5.0.2(@types/node@22.14.0)': + '@inquirer/ansi@1.0.2': {} + + '@inquirer/confirm@5.1.21(@types/node@24.10.4)': dependencies: - '@inquirer/core': 10.1.0(@types/node@22.14.0) - '@inquirer/type': 3.0.1(@types/node@22.14.0) - '@types/node': 22.14.0 + '@inquirer/core': 10.3.2(@types/node@24.10.4) + '@inquirer/type': 3.0.10(@types/node@24.10.4) + optionalDependencies: + '@types/node': 24.10.4 - '@inquirer/core@10.1.0(@types/node@22.14.0)': + '@inquirer/core@10.3.2(@types/node@24.10.4)': dependencies: - '@inquirer/figures': 1.0.8 - '@inquirer/type': 3.0.1(@types/node@22.14.0) - ansi-escapes: 4.3.2 + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.10.4) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 - strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - transitivePeerDependencies: - - '@types/node' + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.10.4 - '@inquirer/figures@1.0.8': {} + '@inquirer/figures@1.0.15': {} - '@inquirer/type@3.0.1(@types/node@22.14.0)': + '@inquirer/type@3.0.10(@types/node@24.10.4)': + optionalDependencies: + '@types/node': 24.10.4 + + '@ioredis/commands@1.4.0': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': dependencies: - '@types/node': 22.14.0 - - '@ioredis/commands@1.2.0': {} + '@isaacs/balanced-match': 4.0.1 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -12999,7 +13375,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.1 + js-yaml: 3.14.2 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.3': {} @@ -13007,7 +13383,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -13020,14 +13396,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 ansi-escapes: 4.3.2 chalk: 4.1.2 - ci-info: 3.7.1 + 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.14.0) + jest-config: 29.7.0(@types/node@24.10.4) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -13048,15 +13424,15 @@ snapshots: - supports-color - ts-node - '@jest/create-cache-key-function@29.7.0': + '@jest/create-cache-key-function@30.2.0': dependencies: - '@jest/types': 29.6.3 + '@jest/types': 30.2.0 '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -13074,7 +13450,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.14.0 + '@types/node': 24.10.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -13088,6 +13464,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 24.10.4 + jest-regex-util: 30.0.1 + '@jest/reporters@29.7.0': dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -13095,25 +13476,25 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.14.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 24.10.4 chalk: 4.1.2 - collect-v8-coverage: 1.0.1 + collect-v8-coverage: 1.0.3 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.0 + istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 + istanbul-reports: 3.2.0 jest-message-util: 29.7.0 jest-util: 29.7.0 jest-worker: 29.7.0 slash: 3.0.0 string-length: 4.0.2 strip-ansi: 6.0.1 - v8-to-istanbul: 9.2.0 + v8-to-istanbul: 9.3.0 transitivePeerDependencies: - supports-color @@ -13121,9 +13502,13 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.8 + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.41 + '@jest/source-map@29.6.3': dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -13131,8 +13516,8 @@ snapshots: dependencies: '@jest/console': 29.7.0 '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.1 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 '@jest/test-sequencer@29.7.0': dependencies: @@ -13143,9 +13528,9 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -13155,7 +13540,7 @@ snapshots: jest-regex-util: 29.6.3 jest-util: 29.7.0 micromatch: 4.0.8 - pirates: 4.0.5 + pirates: 4.0.7 slash: 3.0.0 write-file-atomic: 4.0.2 transitivePeerDependencies: @@ -13164,170 +13549,201 @@ snapshots: '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 22.14.0 - '@types/yargs': 17.0.19 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 24.10.4 + '@types/yargs': 17.0.34 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.3)(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))': + '@jest/types@30.2.0': dependencies: - glob: 10.3.10 - magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@5.8.3) - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 24.10.4 + '@types/yargs': 17.0.34 + chalk: 4.1.2 + + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.3(typescript@5.9.3)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': + dependencies: + glob: 11.1.0 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.0': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/source-map@0.3.6': + '@jridgewell/remapping@2.3.5': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/source-map@0.3.11': dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jsdevtools/ono@7.1.3': {} + '@jridgewell/sourcemap-codec@1.5.5': {} - '@kurkle/color@0.3.2': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 - '@levischuck/tiny-cbor@0.2.2': {} + '@keyv/serialize@1.1.1': {} - '@lukeed/csprng@1.0.1': {} + '@kitajs/html@4.2.11': + dependencies: + csstype: 3.2.3 + + '@kitajs/ts-html-plugin@4.1.3(@kitajs/html@4.2.11)(typescript@5.9.3)': + dependencies: + '@kitajs/html': 4.2.11 + chalk: 5.6.2 + tslib: 2.8.1 + typescript: 5.9.3 + yargs: 18.0.0 + + '@kurkle/color@0.3.4': {} + + '@levischuck/tiny-cbor@0.2.11': {} + + '@lukeed/csprng@1.1.0': {} '@lukeed/ms@2.0.2': {} '@mapbox/node-pre-gyp@1.0.9(encoding@0.1.13)': dependencies: - detect-libc: 2.0.3 + detect-libc: 2.1.2 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0(encoding@0.1.13) nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.6.3 + semver: 7.7.3 tar: 6.2.1 transitivePeerDependencies: - encoding - supports-color optional: true - '@mcaptcha/core-glue@0.1.0-alpha-5': {} + '@mcaptcha/core-glue@0.1.0-rc1': {} - '@mcaptcha/vanilla-glue@0.1.0-alpha-3': + '@mcaptcha/vanilla-glue@0.1.0-rc2(bufferutil@4.1.0)(utf-8-validate@6.0.6)': dependencies: - '@mcaptcha/core-glue': 0.1.0-alpha-5 + '@mcaptcha/core-glue': 0.1.0-rc1 + jest-environment-jsdom: 29.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate - '@mdx-js/react@3.0.1(@types/react@18.0.28)(react@19.1.0)': + '@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.3)': dependencies: - '@types/mdx': 2.0.3 - '@types/react': 18.0.28 - react: 19.1.0 + '@types/mdx': 2.0.13 + '@types/react': 19.2.2 + react: 19.2.3 - '@microsoft/api-extractor-model@7.30.5(@types/node@22.13.15)': + '@microsoft/api-extractor-model@7.32.2(@types/node@24.10.4)': dependencies: - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.13.0(@types/node@22.13.15) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.1(@types/node@24.10.4) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.52.2(@types/node@22.13.15)': + '@microsoft/api-extractor@7.55.2(@types/node@24.10.4)': dependencies: - '@microsoft/api-extractor-model': 7.30.5(@types/node@22.13.15) - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.13.0(@types/node@22.13.15) - '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.15.2(@types/node@22.13.15) - '@rushstack/ts-command-line': 4.23.7(@types/node@22.13.15) + '@microsoft/api-extractor-model': 7.32.2(@types/node@24.10.4) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.1(@types/node@24.10.4) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.19.5(@types/node@24.10.4) + '@rushstack/ts-command-line': 5.1.5(@types/node@24.10.4) + diff: 8.0.2 lodash: 4.17.21 - minimatch: 3.0.8 - resolve: 1.22.8 + minimatch: 10.0.3 + resolve: 1.22.11 semver: 7.5.4 source-map: 0.6.1 typescript: 5.8.2 transitivePeerDependencies: - '@types/node' - '@microsoft/tsdoc-config@0.17.1': + '@microsoft/tsdoc-config@0.18.0': dependencies: - '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc': 0.16.0 ajv: 8.12.0 jju: 1.4.0 - resolve: 1.22.8 + resolve: 1.22.11 - '@microsoft/tsdoc@0.15.1': {} + '@microsoft/tsdoc@0.16.0': {} '@misskey-dev/browser-image-resizer@2024.1.0': {} - '@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0)(typescript@5.3.3))(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0))(eslint@9.22.0)': + '@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2)(typescript@5.3.3))(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2))(eslint@9.39.2)': dependencies: - '@typescript-eslint/eslint-plugin': 6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0)(typescript@5.3.3) - '@typescript-eslint/parser': 6.18.1(eslint@9.22.0)(typescript@5.3.3) - eslint: 9.22.0 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0) + '@typescript-eslint/eslint-plugin': 6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2)(typescript@5.3.3) + '@typescript-eslint/parser': 6.18.1(eslint@9.39.2)(typescript@5.3.3) + eslint: 9.39.2 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2) - '@misskey-dev/eslint-plugin@2.1.0(@eslint/compat@1.1.1)(@stylistic/eslint-plugin@2.13.0(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0))(eslint@9.22.0)(globals@16.0.0)': + '@misskey-dev/eslint-plugin@2.2.0(@eslint/compat@1.4.0(eslint@9.39.2))(@eslint/js@9.39.2)(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2)(globals@16.5.0)': dependencies: - '@eslint/compat': 1.1.1 - '@stylistic/eslint-plugin': 2.13.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - eslint: 9.22.0 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0) - globals: 16.0.0 + '@eslint/compat': 1.4.0(eslint@9.39.2) + '@eslint/js': 9.39.2 + '@stylistic/eslint-plugin': 5.5.0(eslint@9.39.2) + '@typescript-eslint/eslint-plugin': 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) + globals: 16.5.0 - '@misskey-dev/sharp-read-bmp@1.3.0': + '@misskey-dev/sharp-read-bmp@1.2.0': dependencies: decode-bmp: 0.2.1 decode-ico: 0.4.1 - sharp: 0.34.1 + sharp: 0.33.5 - '@misskey-dev/summaly@5.2.0': + '@misskey-dev/summaly@5.2.5': dependencies: - cheerio: 1.0.0 + cheerio: 1.1.2 escape-regexp: 0.0.1 - got: 14.4.7 - html-entities: 2.5.2 - iconv-lite: 0.6.3 + got: 14.6.5 + html-entities: 2.6.0 + iconv-lite: 0.7.0 jschardet: 3.1.4 private-ip: 3.0.2 - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2': + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2': + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2': + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2': + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2': + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2': + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@mswjs/interceptors@0.37.5': + '@mswjs/interceptors@0.40.0': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -13336,93 +13752,172 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/canvas-android-arm64@0.1.69': + '@napi-rs/canvas-android-arm64@0.1.87': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.69': + '@napi-rs/canvas-darwin-arm64@0.1.87': optional: true - '@napi-rs/canvas-darwin-x64@0.1.69': + '@napi-rs/canvas-darwin-x64@0.1.87': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.69': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.87': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.69': + '@napi-rs/canvas-linux-arm64-gnu@0.1.87': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.69': + '@napi-rs/canvas-linux-arm64-musl@0.1.87': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.69': + '@napi-rs/canvas-linux-riscv64-gnu@0.1.87': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.69': + '@napi-rs/canvas-linux-x64-gnu@0.1.87': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.69': + '@napi-rs/canvas-linux-x64-musl@0.1.87': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.69': + '@napi-rs/canvas-win32-arm64-msvc@0.1.87': optional: true - '@napi-rs/canvas@0.1.69': + '@napi-rs/canvas-win32-x64-msvc@0.1.87': + optional: true + + '@napi-rs/canvas@0.1.87': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.69 - '@napi-rs/canvas-darwin-arm64': 0.1.69 - '@napi-rs/canvas-darwin-x64': 0.1.69 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.69 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.69 - '@napi-rs/canvas-linux-arm64-musl': 0.1.69 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.69 - '@napi-rs/canvas-linux-x64-gnu': 0.1.69 - '@napi-rs/canvas-linux-x64-musl': 0.1.69 - '@napi-rs/canvas-win32-x64-msvc': 0.1.69 + '@napi-rs/canvas-android-arm64': 0.1.87 + '@napi-rs/canvas-darwin-arm64': 0.1.87 + '@napi-rs/canvas-darwin-x64': 0.1.87 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.87 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.87 + '@napi-rs/canvas-linux-arm64-musl': 0.1.87 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.87 + '@napi-rs/canvas-linux-x64-gnu': 0.1.87 + '@napi-rs/canvas-linux-x64-musl': 0.1.87 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.87 + '@napi-rs/canvas-win32-x64-msvc': 0.1.87 - '@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@napi-rs/nice-android-arm-eabi@1.1.1': + optional: true + + '@napi-rs/nice-android-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.1.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.1.1': + optional: true + + '@napi-rs/nice-openharmony-arm64@1.1.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + optional: true + + '@napi-rs/nice@1.1.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 + optional: true + + '@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - file-type: 19.6.0 + file-type: 21.1.1 iterare: 1.2.1 + load-esm: 1.0.3 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 uid: 2.0.2 + transitivePeerDependencies: + - supports-color - '@nestjs/core@11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.10)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 - path-to-regexp: 8.2.0 + path-to-regexp: 8.3.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.4.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15) + '@nestjs/platform-express': 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10) - '@nestjs/platform-express@10.4.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15)': + '@nestjs/platform-express@11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10)': dependencies: - '@nestjs/common': 11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2) - body-parser: 1.20.3 + '@nestjs/common': 11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.10)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.5 - express: 4.21.2 - multer: 1.4.4-lts.1 + express: 5.2.1 + multer: 2.0.2 + path-to-regexp: 8.3.0 tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@nestjs/testing@11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15)(@nestjs/platform-express@10.4.15)': + '@nestjs/testing@11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10)(@nestjs/platform-express@11.1.10)': dependencies: - '@nestjs/common': 11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.0.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.10)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 10.4.15(@nestjs/common@11.0.16(file-type@19.6.0)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.15) + '@nestjs/platform-express': 11.1.10(@nestjs/common@11.1.10(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.10) - '@noble/hashes@1.7.1': {} + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -13434,25 +13929,25 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.1 - '@npmcli/agent@2.2.0': + '@npmcli/agent@4.0.0': dependencies: - agent-base: 7.1.3 + agent-base: 7.1.4 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 10.4.3 - socks-proxy-agent: 8.0.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + lru-cache: 11.2.4 + socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color - '@npmcli/fs@3.1.0': + '@npmcli/fs@5.0.0': dependencies: - semver: 7.7.1 + semver: 7.7.3 '@nuxt/opencollective@0.4.1': dependencies: - consola: 3.4.0 + consola: 3.4.2 '@one-ini/wasm@0.1.1': {} @@ -13465,691 +13960,738 @@ snapshots: '@open-draft/until@2.1.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': + '@opentelemetry/api-logs@0.208.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/semantic-conventions': 1.38.0 - '@opentelemetry/instrumentation-amqplib@0.46.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-amqplib@0.55.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/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.43.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-connect@0.52.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 - '@types/connect': 3.4.36 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 + '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.16.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-dataloader@0.26.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.47.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-express@0.57.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/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fastify@0.44.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fs@0.28.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/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.19.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-generic-pool@0.52.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.43.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-graphql@0.56.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.47.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-hapi@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.45.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.208.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 - 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 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 forwarded-parse: 2.1.2 - semver: 7.6.3 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.47.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-ioredis@0.56.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.7.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-kafkajs@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.28.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-knex@0.44.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-knex@0.53.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.47.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-koa@0.57.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/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.44.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-lru-memoizer@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.51.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongodb@0.61.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.46.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongoose@0.55.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/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.45.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql2@0.55.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/sql-common': 0.40.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.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)': + '@opentelemetry/instrumentation-mysql@0.54.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 - '@types/mysql': 2.15.26 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@types/mysql': 2.15.27 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.44.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-pg@0.61.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 - 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 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.6 '@types/pg-pool': 2.0.6 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis-4@0.46.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-redis@0.57.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.18.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.27.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/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.10.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-undici@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) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.208.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 - require-in-the-middle: 7.3.0 - semver: 7.6.3 - shimmer: 1.2.1 + '@opentelemetry/api-logs': 0.208.0 + import-in-the-middle: 2.0.0 + require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.57.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/redis-common@0.38.2': {} + + '@opentelemetry/resources@2.2.0(@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.11.2 - require-in-the-middle: 7.3.0 - semver: 7.6.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 - '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.2 - '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.2 - require-in-the-middle: 7.3.0 - semver: 7.6.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 - '@opentelemetry/redis-common@0.36.2': {} + '@opentelemetry/semantic-conventions@1.38.0': {} - '@opentelemetry/resources@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': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': + '@paralleldrive/cuid2@2.3.1': 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 + '@noble/hashes': 1.8.0 - '@opentelemetry/semantic-conventions@1.27.0': {} - - '@opentelemetry/semantic-conventions@1.28.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) - - '@parcel/watcher-android-arm64@2.5.0': + '@parcel/watcher-android-arm64@2.5.1': optional: true - '@parcel/watcher-darwin-arm64@2.5.0': + '@parcel/watcher-darwin-arm64@2.5.1': optional: true - '@parcel/watcher-darwin-x64@2.5.0': + '@parcel/watcher-darwin-x64@2.5.1': optional: true - '@parcel/watcher-freebsd-x64@2.5.0': + '@parcel/watcher-freebsd-x64@2.5.1': optional: true - '@parcel/watcher-linux-arm-glibc@2.5.0': + '@parcel/watcher-linux-arm-glibc@2.5.1': optional: true - '@parcel/watcher-linux-arm-musl@2.5.0': + '@parcel/watcher-linux-arm-musl@2.5.1': optional: true - '@parcel/watcher-linux-arm64-glibc@2.5.0': + '@parcel/watcher-linux-arm64-glibc@2.5.1': optional: true - '@parcel/watcher-linux-arm64-musl@2.5.0': + '@parcel/watcher-linux-arm64-musl@2.5.1': optional: true - '@parcel/watcher-linux-x64-glibc@2.5.0': + '@parcel/watcher-linux-x64-glibc@2.5.1': optional: true - '@parcel/watcher-linux-x64-musl@2.5.0': + '@parcel/watcher-linux-x64-musl@2.5.1': optional: true - '@parcel/watcher-win32-arm64@2.5.0': + '@parcel/watcher-win32-arm64@2.5.1': optional: true - '@parcel/watcher-win32-ia32@2.5.0': + '@parcel/watcher-win32-ia32@2.5.1': optional: true - '@parcel/watcher-win32-x64@2.5.0': + '@parcel/watcher-win32-x64@2.5.1': optional: true - '@parcel/watcher@2.5.0': + '@parcel/watcher@2.5.1': dependencies: detect-libc: 1.0.3 is-glob: 4.0.3 micromatch: 4.0.8 node-addon-api: 7.1.1 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.0 - '@parcel/watcher-darwin-arm64': 2.5.0 - '@parcel/watcher-darwin-x64': 2.5.0 - '@parcel/watcher-freebsd-x64': 2.5.0 - '@parcel/watcher-linux-arm-glibc': 2.5.0 - '@parcel/watcher-linux-arm-musl': 2.5.0 - '@parcel/watcher-linux-arm64-glibc': 2.5.0 - '@parcel/watcher-linux-arm64-musl': 2.5.0 - '@parcel/watcher-linux-x64-glibc': 2.5.0 - '@parcel/watcher-linux-x64-musl': 2.5.0 - '@parcel/watcher-win32-arm64': 2.5.0 - '@parcel/watcher-win32-ia32': 2.5.0 - '@parcel/watcher-win32-x64': 2.5.0 + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 optional: true - '@peculiar/asn1-android@2.3.10': + '@peculiar/asn1-android@2.6.0': dependencies: - '@peculiar/asn1-schema': 2.3.8 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.3.8': + '@peculiar/asn1-cms@2.6.0': dependencies: - '@peculiar/asn1-schema': 2.3.8 - '@peculiar/asn1-x509': 2.3.8 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509-attr': 2.6.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.3.8': + '@peculiar/asn1-csr@2.6.0': dependencies: - '@peculiar/asn1-schema': 2.3.8 - '@peculiar/asn1-x509': 2.3.8 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-schema@2.3.8': + '@peculiar/asn1-ecc@2.6.0': dependencies: - asn1js: 3.0.5 - pvtsutils: 1.3.5 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-x509@2.3.8': + '@peculiar/asn1-pfx@2.6.0': dependencies: - '@peculiar/asn1-schema': 2.3.8 - asn1js: 3.0.5 - ipaddr.js: 2.2.0 - pvtsutils: 1.3.5 + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-pkcs8': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.6 tslib: 2.8.1 + '@peculiar/asn1-pkcs8@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.6.0': + dependencies: + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-pfx': 2.6.0 + '@peculiar/asn1-pkcs8': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509-attr': 2.6.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.6.0': + dependencies: + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/x509@1.14.2': + dependencies: + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-csr': 2.6.0 + '@peculiar/asn1-ecc': 2.6.0 + '@peculiar/asn1-pkcs9': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 + '@peertube/http-signature@1.7.0': dependencies: assert-plus: 1.0.0 jsprim: 1.4.2 - sshpk: 1.17.0 + sshpk: 1.18.0 + + '@pinojs/redact@0.4.0': {} '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/instrumentation@5.22.0': + '@prisma/instrumentation@6.19.0(@opentelemetry/api@1.9.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) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@readme/better-ajv-errors@2.2.2(ajv@8.17.1)': + '@readme/better-ajv-errors@2.4.0(ajv@8.17.1)': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.23.4 + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 '@humanwhocodes/momoa': 2.0.4 ajv: 8.17.1 jsonpointer: 5.0.1 leven: 3.1.0 picocolors: 1.1.1 - '@readme/json-schema-ref-parser@1.2.0': + '@readme/openapi-parser@5.4.0(openapi-types@12.1.3)': dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - call-me-maybe: 1.0.2 - js-yaml: 4.1.0 - - '@readme/openapi-parser@2.7.0(openapi-types@12.1.3)': - dependencies: - '@apidevtools/swagger-methods': 3.0.2 - '@jsdevtools/ono': 7.1.3 - '@readme/better-ajv-errors': 2.2.2(ajv@8.17.1) - '@readme/json-schema-ref-parser': 1.2.0 + '@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15) + '@readme/better-ajv-errors': 2.4.0(ajv@8.17.1) '@readme/openapi-schemas': 3.1.0 + '@types/json-schema': 7.0.15 ajv: 8.17.1 ajv-draft-04: 1.0.0(ajv@8.17.1) - call-me-maybe: 1.0.2 openapi-types: 12.1.3 '@readme/openapi-schemas@3.1.0': {} - '@rollup/plugin-json@6.1.0(rollup@4.39.0)': + '@redocly/ajv@8.17.1': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.39.0) - optionalDependencies: - rollup: 4.39.0 + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 - '@rollup/plugin-replace@6.0.2(rollup@4.39.0)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.39.0) - magic-string: 0.30.17 - optionalDependencies: - rollup: 4.39.0 + '@redocly/config@0.22.2': {} - '@rollup/pluginutils@5.1.4(rollup@4.39.0)': + '@redocly/openapi-core@1.34.5(supports-color@10.2.2)': dependencies: - '@types/estree': 1.0.7 + '@redocly/ajv': 8.17.1 + '@redocly/config': 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@rollup/plugin-json@6.1.0(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-replace@6.0.3(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/pluginutils@5.3.0(rollup@4.54.0)': + dependencies: + '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.2 + picomatch: 4.0.3 optionalDependencies: - rollup: 4.39.0 + rollup: 4.54.0 - '@rollup/rollup-android-arm-eabi@4.39.0': + '@rollup/rollup-android-arm-eabi@4.54.0': optional: true - '@rollup/rollup-android-arm64@4.39.0': + '@rollup/rollup-android-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-arm64@4.39.0': + '@rollup/rollup-darwin-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-x64@4.39.0': + '@rollup/rollup-darwin-x64@4.54.0': optional: true - '@rollup/rollup-freebsd-arm64@4.39.0': + '@rollup/rollup-freebsd-arm64@4.54.0': optional: true - '@rollup/rollup-freebsd-x64@4.39.0': + '@rollup/rollup-freebsd-x64@4.54.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.39.0': + '@rollup/rollup-linux-arm-musleabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.39.0': + '@rollup/rollup-linux-arm64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.39.0': + '@rollup/rollup-linux-arm64-musl@4.54.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + '@rollup/rollup-linux-loong64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + '@rollup/rollup-linux-ppc64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.39.0': + '@rollup/rollup-linux-riscv64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.39.0': + '@rollup/rollup-linux-riscv64-musl@4.54.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.39.0': + '@rollup/rollup-linux-s390x-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.39.0': + '@rollup/rollup-linux-x64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-musl@4.39.0': + '@rollup/rollup-linux-x64-musl@4.54.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.39.0': + '@rollup/rollup-openharmony-arm64@4.54.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.39.0': + '@rollup/rollup-win32-arm64-msvc@4.54.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.39.0': + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.13.0(@types/node@22.13.15)': + '@rushstack/node-core-library@5.19.1(@types/node@24.10.4)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.0 + fs-extra: 11.3.2 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.8 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: - '@types/node': 22.13.15 + '@types/node': 24.10.4 - '@rushstack/rig-package@0.5.3': + '@rushstack/problem-matcher@0.1.1(@types/node@24.10.4)': + optionalDependencies: + '@types/node': 24.10.4 + + '@rushstack/rig-package@0.6.0': dependencies: - resolve: 1.22.8 + resolve: 1.22.11 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.15.2(@types/node@22.13.15)': + '@rushstack/terminal@0.19.5(@types/node@24.10.4)': dependencies: - '@rushstack/node-core-library': 5.13.0(@types/node@22.13.15) + '@rushstack/node-core-library': 5.19.1(@types/node@24.10.4) + '@rushstack/problem-matcher': 0.1.1(@types/node@24.10.4) supports-color: 8.1.1 optionalDependencies: - '@types/node': 22.13.15 + '@types/node': 24.10.4 - '@rushstack/ts-command-line@4.23.7(@types/node@22.13.15)': + '@rushstack/ts-command-line@5.1.5(@types/node@24.10.4)': dependencies: - '@rushstack/terminal': 0.15.2(@types/node@22.13.15) + '@rushstack/terminal': 0.19.5(@types/node@24.10.4) '@types/argparse': 1.0.38 argparse: 1.0.10 - string-argv: 0.3.1 + string-argv: 0.3.2 transitivePeerDependencies: - '@types/node' '@sec-ant/readable-stream@0.4.1': {} - '@sentry-internal/browser-utils@9.12.0': + '@sentry-internal/browser-utils@10.32.1': dependencies: - '@sentry/core': 9.12.0 + '@sentry/core': 10.32.1 - '@sentry-internal/feedback@9.12.0': + '@sentry-internal/feedback@10.32.1': dependencies: - '@sentry/core': 9.12.0 + '@sentry/core': 10.32.1 - '@sentry-internal/replay-canvas@9.12.0': + '@sentry-internal/node-cpu-profiler@2.2.0': dependencies: - '@sentry-internal/replay': 9.12.0 - '@sentry/core': 9.12.0 + detect-libc: 2.1.2 + node-abi: 3.85.0 - '@sentry-internal/replay@9.12.0': + '@sentry-internal/replay-canvas@10.32.1': dependencies: - '@sentry-internal/browser-utils': 9.12.0 - '@sentry/core': 9.12.0 + '@sentry-internal/replay': 10.32.1 + '@sentry/core': 10.32.1 - '@sentry/browser@9.12.0': + '@sentry-internal/replay@10.32.1': dependencies: - '@sentry-internal/browser-utils': 9.12.0 - '@sentry-internal/feedback': 9.12.0 - '@sentry-internal/replay': 9.12.0 - '@sentry-internal/replay-canvas': 9.12.0 - '@sentry/core': 9.12.0 + '@sentry-internal/browser-utils': 10.32.1 + '@sentry/core': 10.32.1 - '@sentry/core@8.55.0': {} - - '@sentry/core@9.12.0': {} - - '@sentry/node@8.55.0': + '@sentry/browser@10.32.1': dependencies: + '@sentry-internal/browser-utils': 10.32.1 + '@sentry-internal/feedback': 10.32.1 + '@sentry-internal/replay': 10.32.1 + '@sentry-internal/replay-canvas': 10.32.1 + '@sentry/core': 10.32.1 + + '@sentry/core@10.32.1': {} + + '@sentry/node-core@10.32.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': + dependencies: + '@apm-js-collab/tracing-hooks': 0.3.1 '@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.28.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 + '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 + '@sentry/core': 10.32.1 + '@sentry/opentelemetry': 10.32.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + import-in-the-middle: 2.0.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/node@10.32.1': 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 - '@sentry/core': 8.55.0 - - '@sentry/profiling-node@8.55.0': - dependencies: - '@sentry/core': 8.55.0 - '@sentry/node': 8.55.0 - detect-libc: 2.0.3 - node-abi: 3.62.0 + '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.26.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.18.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.61.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.61.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.19.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 + '@prisma/instrumentation': 6.19.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.32.1 + '@sentry/node-core': 10.32.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + '@sentry/opentelemetry': 10.32.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + import-in-the-middle: 2.0.0 + minimatch: 9.0.4 transitivePeerDependencies: - supports-color - '@sentry/vue@9.12.0(vue@3.5.13(typescript@5.8.3))': + '@sentry/opentelemetry@10.32.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': dependencies: - '@sentry/browser': 9.12.0 - '@sentry/core': 9.12.0 - vue: 3.5.13(typescript@5.8.3) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 + '@sentry/core': 10.32.1 - '@shikijs/core@3.2.2': + '@sentry/profiling-node@10.32.1': dependencies: - '@shikijs/types': 3.2.2 + '@sentry-internal/node-cpu-profiler': 2.2.0 + '@sentry/core': 10.32.1 + '@sentry/node': 10.32.1 + transitivePeerDependencies: + - supports-color + + '@sentry/vue@10.32.1(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@sentry/browser': 10.32.1 + '@sentry/core': 10.32.1 + vue: 3.5.26(typescript@5.9.3) + + '@shikijs/core@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.2.2': + '@shikijs/engine-javascript@3.20.0': dependencies: - '@shikijs/types': 3.2.2 + '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.1.0 + oniguruma-to-es: 4.3.4 - '@shikijs/engine-oniguruma@3.2.2': + '@shikijs/engine-oniguruma@3.20.0': dependencies: - '@shikijs/types': 3.2.2 + '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.2.2': + '@shikijs/langs@3.20.0': dependencies: - '@shikijs/types': 3.2.2 + '@shikijs/types': 3.20.0 - '@shikijs/themes@3.2.2': + '@shikijs/themes@3.20.0': dependencies: - '@shikijs/types': 3.2.2 + '@shikijs/types': 3.20.0 - '@shikijs/types@3.2.2': + '@shikijs/types@3.20.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 '@shikijs/vscode-textmate@10.0.2': {} - '@sideway/address@4.1.4': - dependencies: - '@hapi/hoek': 9.3.0 - '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 @@ -14158,51 +14700,38 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@simplewebauthn/server@12.0.0(encoding@0.1.13)': + '@simplewebauthn/server@13.2.2': dependencies: - '@hexagon/base64': 1.1.27 - '@levischuck/tiny-cbor': 0.2.2 - '@peculiar/asn1-android': 2.3.10 - '@peculiar/asn1-ecc': 2.3.8 - '@peculiar/asn1-rsa': 2.3.8 - '@peculiar/asn1-schema': 2.3.8 - '@peculiar/asn1-x509': 2.3.8 - '@simplewebauthn/types': 12.0.0 - cross-fetch: 4.1.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.11 + '@peculiar/asn1-android': 2.6.0 + '@peculiar/asn1-ecc': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/x509': 1.14.2 '@simplewebauthn/types@12.0.0': {} '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.34.41': {} + '@sindresorhus/is@5.6.0': {} - '@sindresorhus/is@7.0.1': {} + '@sindresorhus/is@7.1.1': {} '@sindresorhus/merge-streams@4.0.0': {} - '@sinonjs/commons@2.0.0': - dependencies: - type-detect: 4.0.8 - - '@sinonjs/commons@3.0.0': - dependencies: - type-detect: 4.0.8 - '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 '@sinonjs/fake-timers@10.3.0': - dependencies: - '@sinonjs/commons': 3.0.0 - - '@sinonjs/fake-timers@11.2.2': dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers@11.3.1': + '@sinonjs/fake-timers@11.2.2': dependencies: '@sinonjs/commons': 3.0.1 @@ -14210,694 +14739,675 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/samsam@8.0.0': + '@sinonjs/fake-timers@15.1.0': dependencies: - '@sinonjs/commons': 2.0.0 - lodash.get: 4.4.2 - type-detect: 4.0.8 + '@sinonjs/commons': 3.0.1 + + '@sinonjs/samsam@8.0.3': + dependencies: + '@sinonjs/commons': 3.0.1 + type-detect: 4.1.0 '@sinonjs/text-encoding@0.7.3': {} - '@smithy/abort-controller@2.2.0': + '@smithy/abort-controller@4.2.7': dependencies: - '@smithy/types': 2.12.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/abort-controller@4.0.2': + '@smithy/chunked-blob-reader-native@4.2.1': dependencies: - '@smithy/types': 4.2.0 + '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/chunked-blob-reader-native@4.0.0': - dependencies: - '@smithy/util-base64': 4.0.0 - tslib: 2.8.1 - - '@smithy/chunked-blob-reader@5.0.0': + '@smithy/chunked-blob-reader@5.2.0': dependencies: tslib: 2.8.1 - '@smithy/config-resolver@4.1.0': + '@smithy/config-resolver@4.4.5': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.0.2 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 tslib: 2.8.1 - '@smithy/core@3.2.0': + '@smithy/core@3.20.0': dependencies: - '@smithy/middleware-serde': 4.0.3 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-stream': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/middleware-serde': 4.2.8 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.0.2': + '@smithy/credential-provider-imds@4.2.7': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 tslib: 2.8.1 - '@smithy/eventstream-codec@4.0.2': + '@smithy/eventstream-codec@4.2.7': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-hex-encoding': 4.0.0 + '@smithy/types': 4.11.0 + '@smithy/util-hex-encoding': 4.2.0 tslib: 2.8.1 - '@smithy/eventstream-serde-browser@4.0.2': + '@smithy/eventstream-serde-browser@4.2.7': dependencies: - '@smithy/eventstream-serde-universal': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-serde-universal': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/eventstream-serde-config-resolver@4.1.0': + '@smithy/eventstream-serde-config-resolver@4.3.7': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/eventstream-serde-node@4.0.2': + '@smithy/eventstream-serde-node@4.2.7': dependencies: - '@smithy/eventstream-serde-universal': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-serde-universal': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/eventstream-serde-universal@4.0.2': + '@smithy/eventstream-serde-universal@4.2.7': dependencies: - '@smithy/eventstream-codec': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/eventstream-codec': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.0.2': + '@smithy/fetch-http-handler@5.3.8': dependencies: - '@smithy/protocol-http': 5.1.0 - '@smithy/querystring-builder': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/util-base64': 4.0.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/hash-blob-browser@4.0.2': + '@smithy/hash-blob-browser@4.2.8': dependencies: - '@smithy/chunked-blob-reader': 5.0.0 - '@smithy/chunked-blob-reader-native': 4.0.0 - '@smithy/types': 4.2.0 + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/hash-node@4.0.2': + '@smithy/hash-node@4.2.7': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.11.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/hash-stream-node@4.0.2': + '@smithy/hash-stream-node@4.2.7': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.11.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/invalid-dependency@4.0.2': + '@smithy/invalid-dependency@4.2.7': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/is-array-buffer@2.0.0': + '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 - '@smithy/is-array-buffer@4.0.0': + '@smithy/is-array-buffer@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/md5-js@4.0.2': + '@smithy/md5-js@4.2.7': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/types': 4.11.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/middleware-content-length@4.0.2': + '@smithy/middleware-content-length@4.2.7': dependencies: - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.1.0': + '@smithy/middleware-endpoint@4.4.1': dependencies: - '@smithy/core': 3.2.0 - '@smithy/middleware-serde': 4.0.3 - '@smithy/node-config-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 - '@smithy/url-parser': 4.0.2 - '@smithy/util-middleware': 4.0.2 + '@smithy/core': 3.20.0 + '@smithy/middleware-serde': 4.2.8 + '@smithy/node-config-provider': 4.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-middleware': 4.2.7 tslib: 2.8.1 - '@smithy/middleware-retry@4.1.0': + '@smithy/middleware-retry@4.4.17': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/service-error-classification': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-retry': 4.0.2 - tslib: 2.8.1 - uuid: 9.0.1 - - '@smithy/middleware-serde@4.0.3': - dependencies: - '@smithy/types': 4.2.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/service-error-classification': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/middleware-stack@4.0.2': + '@smithy/middleware-serde@4.2.8': dependencies: - '@smithy/types': 4.2.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/node-config-provider@4.0.2': + '@smithy/middleware-stack@4.2.7': dependencies: - '@smithy/property-provider': 4.0.2 - '@smithy/shared-ini-file-loader': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/node-http-handler@2.5.0': + '@smithy/node-config-provider@4.3.7': dependencies: - '@smithy/abort-controller': 2.2.0 - '@smithy/protocol-http': 3.3.0 - '@smithy/querystring-builder': 2.2.0 - '@smithy/types': 2.12.0 - tslib: 2.6.2 - - '@smithy/node-http-handler@4.0.4': - dependencies: - '@smithy/abort-controller': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/querystring-builder': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/property-provider@4.0.2': + '@smithy/node-http-handler@4.4.7': dependencies: - '@smithy/types': 4.2.0 + '@smithy/abort-controller': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/protocol-http@3.3.0': + '@smithy/property-provider@4.2.7': dependencies: - '@smithy/types': 2.12.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/protocol-http@5.1.0': + '@smithy/protocol-http@5.3.7': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/querystring-builder@2.2.0': + '@smithy/querystring-builder@4.2.7': dependencies: - '@smithy/types': 2.12.0 - '@smithy/util-uri-escape': 2.2.0 + '@smithy/types': 4.11.0 + '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 - '@smithy/querystring-builder@4.0.2': + '@smithy/querystring-parser@4.2.7': dependencies: - '@smithy/types': 4.2.0 - '@smithy/util-uri-escape': 4.0.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/querystring-parser@4.0.2': + '@smithy/service-error-classification@4.2.7': dependencies: - '@smithy/types': 4.2.0 + '@smithy/types': 4.11.0 + + '@smithy/shared-ini-file-loader@4.4.2': + dependencies: + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/service-error-classification@4.0.2': + '@smithy/signature-v4@5.3.7': dependencies: - '@smithy/types': 4.2.0 - - '@smithy/shared-ini-file-loader@4.0.2': - dependencies: - '@smithy/types': 4.2.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/signature-v4@5.0.2': + '@smithy/smithy-client@4.10.2': dependencies: - '@smithy/is-array-buffer': 4.0.0 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-hex-encoding': 4.0.0 - '@smithy/util-middleware': 4.0.2 - '@smithy/util-uri-escape': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/core': 3.20.0 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-stack': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 tslib: 2.8.1 - '@smithy/smithy-client@4.2.0': - dependencies: - '@smithy/core': 3.2.0 - '@smithy/middleware-endpoint': 4.1.0 - '@smithy/middleware-stack': 4.0.2 - '@smithy/protocol-http': 5.1.0 - '@smithy/types': 4.2.0 - '@smithy/util-stream': 4.2.0 - tslib: 2.8.1 - - '@smithy/types@2.12.0': + '@smithy/types@4.11.0': dependencies: tslib: 2.8.1 - '@smithy/types@4.2.0': + '@smithy/url-parser@4.2.7': + dependencies: + '@smithy/querystring-parser': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/url-parser@4.0.2': - dependencies: - '@smithy/querystring-parser': 4.0.2 - '@smithy/types': 4.2.0 - tslib: 2.8.1 - - '@smithy/util-base64@4.0.0': - dependencies: - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-utf8': 4.0.0 - tslib: 2.8.1 - - '@smithy/util-body-length-browser@4.0.0': + '@smithy/util-body-length-node@4.2.1': dependencies: tslib: 2.8.1 - '@smithy/util-body-length-node@4.0.0': + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-buffer-from@2.0.0': + '@smithy/util-defaults-mode-browser@4.3.16': dependencies: - '@smithy/is-array-buffer': 2.0.0 + '@smithy/property-provider': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/util-buffer-from@4.0.0': + '@smithy/util-defaults-mode-node@4.2.19': dependencies: - '@smithy/is-array-buffer': 4.0.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/util-config-provider@4.0.0': + '@smithy/util-endpoints@3.2.7': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.0.8': + '@smithy/util-middleware@4.2.7': dependencies: - '@smithy/property-provider': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 - bowser: 2.11.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.0.8': + '@smithy/util-retry@4.2.7': dependencies: - '@smithy/config-resolver': 4.1.0 - '@smithy/credential-provider-imds': 4.0.2 - '@smithy/node-config-provider': 4.0.2 - '@smithy/property-provider': 4.0.2 - '@smithy/smithy-client': 4.2.0 - '@smithy/types': 4.2.0 + '@smithy/service-error-classification': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/util-endpoints@3.0.2': + '@smithy/util-stream@4.5.8': dependencies: - '@smithy/node-config-provider': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-hex-encoding@4.0.0': + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@4.0.2': + '@smithy/util-utf8@2.3.0': dependencies: - '@smithy/types': 4.2.0 + '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@smithy/util-retry@4.0.2': + '@smithy/util-utf8@4.2.0': dependencies: - '@smithy/service-error-classification': 4.0.2 - '@smithy/types': 4.2.0 + '@smithy/util-buffer-from': 4.2.0 tslib: 2.8.1 - '@smithy/util-stream@4.2.0': + '@smithy/util-waiter@4.2.7': dependencies: - '@smithy/fetch-http-handler': 5.0.2 - '@smithy/node-http-handler': 4.0.4 - '@smithy/types': 4.2.0 - '@smithy/util-base64': 4.0.0 - '@smithy/util-buffer-from': 4.0.0 - '@smithy/util-hex-encoding': 4.0.0 - '@smithy/util-utf8': 4.0.0 + '@smithy/abort-controller': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/util-uri-escape@2.2.0': + '@smithy/uuid@1.1.0': dependencies: tslib: 2.8.1 - '@smithy/util-uri-escape@4.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-utf8@2.0.0': - dependencies: - '@smithy/util-buffer-from': 2.0.0 - tslib: 2.8.1 - - '@smithy/util-utf8@4.0.0': - dependencies: - '@smithy/util-buffer-from': 4.0.0 - tslib: 2.8.1 - - '@smithy/util-waiter@4.0.3': - dependencies: - '@smithy/abort-controller': 4.0.2 - '@smithy/types': 4.2.0 - tslib: 2.8.1 - '@sqltools/formatter@1.2.5': {} - '@storybook/addon-actions@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@standard-schema/spec@1.0.0': {} + + '@storybook/addon-actions@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 - polished: 4.2.2 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + polished: 4.3.1 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) uuid: 9.0.1 - '@storybook/addon-backgrounds@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-backgrounds@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - '@storybook/addon-controls@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-controls@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - '@storybook/addon-docs@8.6.12(@types/react@18.0.28)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-docs@8.6.15(@types/react@19.2.2)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@19.1.0) - '@storybook/blocks': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/csf-plugin': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/react-dom-shim': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@19.2.3) + '@storybook/blocks': 8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/csf-plugin': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/react-dom-shim': 8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-essentials@8.6.12(@types/react@18.0.28)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-essentials@8.6.15(@types/react@19.2.2)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - '@storybook/addon-actions': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-backgrounds': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-controls': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-docs': 8.6.12(@types/react@18.0.28)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-highlight': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-measure': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-outline': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-toolbars': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/addon-viewport': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@storybook/addon-actions': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-backgrounds': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-controls': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-docs': 8.6.15(@types/react@19.2.2)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-highlight': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-measure': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-outline': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-toolbars': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/addon-viewport': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-highlight@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-highlight@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/addon-interactions@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-interactions@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/test': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - polished: 4.2.2 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@storybook/instrumenter': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/test': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + polished: 4.3.1 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - '@storybook/addon-links@8.6.12(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-links@10.1.10(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - ts-dedent: 2.2.0 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) optionalDependencies: - react: 19.1.0 + react: 19.2.3 - '@storybook/addon-mdx-gfm@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-mdx-gfm@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - remark-gfm: 4.0.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + remark-gfm: 4.0.1 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color - '@storybook/addon-measure@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-measure@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) tiny-invariant: 1.3.3 - '@storybook/addon-outline@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-outline@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - '@storybook/addon-storysource@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-storysource@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - '@storybook/source-loader': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + '@storybook/source-loader': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) estraverse: 5.3.0 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) tiny-invariant: 1.3.3 - '@storybook/addon-toolbars@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-toolbars@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/addon-viewport@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/addon-viewport@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: memoizerific: 1.11.3 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/blocks@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/blocks@8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - '@storybook/icons': 1.2.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@storybook/icons': 1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 optionalDependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@storybook/builder-vite@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))': + '@storybook/builder-vite@10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': dependencies: - '@storybook/csf-plugin': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - browser-assert: 1.2.1 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@storybook/csf-plugin': 10.1.10(esbuild@0.27.2)(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + '@vitest/mocker': 3.2.4(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - - '@storybook/components@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': - dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - - '@storybook/core-events@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': - dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - - '@storybook/core@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(utf-8-validate@6.0.5)': - dependencies: - '@storybook/theming': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - better-opn: 3.0.2 - browser-assert: 1.2.1 - esbuild: 0.25.2 - esbuild-register: 3.5.0(esbuild@0.25.2) - jsdoc-type-pratt-parser: 4.1.0 - process: 0.11.10 - recast: 0.23.6 - semver: 7.7.1 - util: 0.12.5 - ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) - optionalDependencies: - prettier: 3.5.3 + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) transitivePeerDependencies: - - bufferutil - - storybook - - supports-color - - utf-8-validate + - esbuild + - msw + - rollup + - webpack - '@storybook/csf-plugin@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/components@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - unplugin: 1.4.0 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + + '@storybook/core-events@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': + dependencies: + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + + '@storybook/csf-plugin@10.1.10(esbuild@0.27.2)(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': + dependencies: + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + unplugin: 2.3.10 + optionalDependencies: + esbuild: 0.27.2 + rollup: 4.54.0 + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + + '@storybook/csf-plugin@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': + dependencies: + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + unplugin: 1.16.1 '@storybook/global@5.0.0': {} - '@storybook/icons@1.2.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@storybook/icons@1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@storybook/instrumenter@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/icons@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@storybook/instrumenter@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - '@vitest/utils': 2.1.1 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@vitest/utils': 2.1.9 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/manager-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/manager-api@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/preview-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/preview-api@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/react-dom-shim@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/react-dom-shim@10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/react-vite@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.39.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.3)(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))': + '@storybook/react-dom-shim@8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.3)(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) - '@rollup/pluginutils': 5.1.4(rollup@4.39.0) - '@storybook/builder-vite': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) - '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.3) - find-up: 5.0.0 - magic-string: 0.30.17 - react: 19.1.0 - react-docgen: 7.0.1 - react-dom: 19.1.0(react@19.1.0) - resolve: 1.22.8 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + + '@storybook/react-vite@10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.3(typescript@5.9.3)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + '@storybook/builder-vite': 10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + '@storybook/react': 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(typescript@5.9.3) + empathic: 2.0.0 + magic-string: 0.30.21 + react: 19.2.3 + react-docgen: 8.0.2 + react-dom: 19.2.3(react@19.2.3) + resolve: 1.22.11 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) tsconfig-paths: 4.2.0 - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - optionalDependencies: - '@storybook/test': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) transitivePeerDependencies: + - esbuild + - msw - rollup - supports-color - typescript + - webpack - '@storybook/react@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.3)': + '@storybook/react@10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(typescript@5.9.3)': dependencies: - '@storybook/components': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/preview-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/react-dom-shim': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/theming': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + '@storybook/react-dom-shim': 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + react: 19.2.3 + react-docgen: 8.0.2 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) optionalDependencies: - '@storybook/test': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - typescript: 5.8.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color - '@storybook/source-loader@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/source-loader@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - es-toolkit: 1.27.0 + es-toolkit: 1.42.0 estraverse: 5.3.0 - prettier: 3.5.3 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + prettier: 3.7.4 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/test@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/test@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + '@storybook/instrumenter': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.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: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/theming@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/theming@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/types@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': + '@storybook/types@8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))': dependencies: - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) - '@storybook/vue3-vite@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.3))': + '@storybook/vue3-vite@10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))': dependencies: - '@storybook/builder-vite': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) - '@storybook/vue3': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.3)) - find-package-json: 1.2.0 - magic-string: 0.30.17 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - typescript: 5.8.3 - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - vue-component-meta: 2.0.16(typescript@5.8.3) - vue-docgen-api: 4.75.1(vue@3.5.13(typescript@5.8.3)) + '@storybook/builder-vite': 10.1.10(esbuild@0.27.2)(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(rollup@4.54.0)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + '@storybook/vue3': 10.1.10(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vue@3.5.26(typescript@5.9.3)) + magic-string: 0.30.21 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) + typescript: 5.9.3 + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + vue-component-meta: 2.2.12(typescript@5.9.3) + vue-docgen-api: 4.79.2(vue@3.5.26(typescript@5.9.3)) transitivePeerDependencies: + - esbuild + - msw + - rollup - vue + - webpack - '@storybook/vue3@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.3))': + '@storybook/vue3@10.1.10(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6))(vue@3.5.26(typescript@5.9.3))': dependencies: - '@storybook/components': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/preview-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/theming': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@vue/compiler-core': 3.5.13 - storybook: 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - ts-dedent: 2.2.0 + storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) type-fest: 2.19.0 - vue: 3.5.13(typescript@5.8.3) - vue-component-type-helpers: 2.2.8 + vue: 3.5.26(typescript@5.9.3) + vue-component-type-helpers: 3.2.2 - '@stylistic/eslint-plugin@2.13.0(eslint@9.22.0)(typescript@5.8.2)': + '@stylistic/eslint-plugin@5.5.0(eslint@9.39.2)': dependencies: - '@typescript-eslint/utils': 8.29.1(eslint@9.22.0)(typescript@5.8.2) - eslint: 9.22.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/types': 8.51.0 + eslint: 9.39.2 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 estraverse: 5.3.0 - picomatch: 4.0.2 - transitivePeerDependencies: - - supports-color - - typescript + picomatch: 4.0.3 - '@swc/cli@0.6.0(@swc/core@1.11.18)(chokidar@4.0.3)': + '@swc/cli@0.7.9(@swc/core@1.15.7)(chokidar@5.0.0)': dependencies: - '@swc/core': 1.11.18 + '@swc/core': 1.15.7 '@swc/counter': 0.1.3 - '@xhmikosr/bin-wrapper': 13.0.5 + '@xhmikosr/bin-wrapper': 13.2.0 commander: 8.3.0 - fast-glob: 3.3.3 minimatch: 9.0.4 - piscina: 4.4.0 - semver: 7.6.3 + piscina: 4.9.2 + semver: 7.7.3 slash: 3.0.0 - source-map: 0.7.4 + source-map: 0.7.6 + tinyglobby: 0.2.15 optionalDependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color '@swc/core-android-arm64@1.3.11': dependencies: '@swc/wasm': 1.2.130 optional: true - '@swc/core-darwin-arm64@1.11.18': + '@swc/core-darwin-arm64@1.15.7': optional: true - '@swc/core-darwin-x64@1.11.18': + '@swc/core-darwin-x64@1.15.7': optional: true '@swc/core-freebsd-x64@1.3.11': @@ -14905,56 +15415,56 @@ snapshots: '@swc/wasm': 1.2.130 optional: true - '@swc/core-linux-arm-gnueabihf@1.11.18': + '@swc/core-linux-arm-gnueabihf@1.15.7': optional: true - '@swc/core-linux-arm64-gnu@1.11.18': + '@swc/core-linux-arm64-gnu@1.15.7': optional: true - '@swc/core-linux-arm64-musl@1.11.18': + '@swc/core-linux-arm64-musl@1.15.7': optional: true - '@swc/core-linux-x64-gnu@1.11.18': + '@swc/core-linux-x64-gnu@1.15.7': optional: true - '@swc/core-linux-x64-musl@1.11.18': + '@swc/core-linux-x64-musl@1.15.7': optional: true - '@swc/core-win32-arm64-msvc@1.11.18': + '@swc/core-win32-arm64-msvc@1.15.7': optional: true - '@swc/core-win32-ia32-msvc@1.11.18': + '@swc/core-win32-ia32-msvc@1.15.7': optional: true - '@swc/core-win32-x64-msvc@1.11.18': + '@swc/core-win32-x64-msvc@1.15.7': optional: true - '@swc/core@1.11.18': + '@swc/core@1.15.7': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.21 + '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.11.18 - '@swc/core-darwin-x64': 1.11.18 - '@swc/core-linux-arm-gnueabihf': 1.11.18 - '@swc/core-linux-arm64-gnu': 1.11.18 - '@swc/core-linux-arm64-musl': 1.11.18 - '@swc/core-linux-x64-gnu': 1.11.18 - '@swc/core-linux-x64-musl': 1.11.18 - '@swc/core-win32-arm64-msvc': 1.11.18 - '@swc/core-win32-ia32-msvc': 1.11.18 - '@swc/core-win32-x64-msvc': 1.11.18 + '@swc/core-darwin-arm64': 1.15.7 + '@swc/core-darwin-x64': 1.15.7 + '@swc/core-linux-arm-gnueabihf': 1.15.7 + '@swc/core-linux-arm64-gnu': 1.15.7 + '@swc/core-linux-arm64-musl': 1.15.7 + '@swc/core-linux-x64-gnu': 1.15.7 + '@swc/core-linux-x64-musl': 1.15.7 + '@swc/core-win32-arm64-msvc': 1.15.7 + '@swc/core-win32-ia32-msvc': 1.15.7 + '@swc/core-win32-x64-msvc': 1.15.7 '@swc/counter@0.1.3': {} - '@swc/jest@0.2.37(@swc/core@1.11.18)': + '@swc/jest@0.2.39(@swc/core@1.15.7)': dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.11.18 + '@jest/create-cache-key-function': 30.2.0 + '@swc/core': 1.15.7 '@swc/counter': 0.1.3 - jsonc-parser: 3.2.0 + jsonc-parser: 3.3.1 - '@swc/types@0.1.21': + '@swc/types@0.1.25': dependencies: '@swc/counter': 0.1.3 @@ -14967,15 +15477,22 @@ snapshots: stringz: 2.1.0 uuid: 9.0.1 + '@syuilo/aiscript@1.2.1': + dependencies: + seedrandom: 3.0.5 + stringz: 2.1.0 + uuid: 11.1.0 + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 - '@tabler/icons-webfont@3.31.0': + '@tabler/icons-webfont@3.35.0': dependencies: - '@tabler/icons': 3.31.0 + '@tabler/icons': 3.35.0 + sharp: 0.33.5 - '@tabler/icons@3.31.0': {} + '@tabler/icons@3.35.0': {} '@tensorflow/tfjs-backend-cpu@4.22.0(@tensorflow/tfjs-core@4.22.0(encoding@0.1.13))': dependencies: @@ -14998,7 +15515,7 @@ snapshots: '@tensorflow/tfjs-core@4.22.0(encoding@0.1.13)': dependencies: '@types/long': 4.0.2 - '@types/offscreencanvas': 2019.7.0 + '@types/offscreencanvas': 2019.7.3 '@types/seedrandom': 2.4.34 '@webgpu/types': 0.1.38 long: 4.0.0 @@ -15010,7 +15527,7 @@ snapshots: '@tensorflow/tfjs-data@4.22.0(@tensorflow/tfjs-core@4.22.0(encoding@0.1.13))(encoding@0.1.13)(seedrandom@3.0.5)': dependencies: '@tensorflow/tfjs-core': 4.22.0(encoding@0.1.13) - '@types/node-fetch': 2.6.11 + '@types/node-fetch': 2.6.13 node-fetch: 2.6.13(encoding@0.1.13) seedrandom: 3.0.5 string_decoder: 1.3.0 @@ -15025,8 +15542,8 @@ snapshots: dependencies: '@mapbox/node-pre-gyp': 1.0.9(encoding@0.1.13) '@tensorflow/tfjs': 4.22.0(encoding@0.1.13)(seedrandom@3.0.5) - adm-zip: 0.5.10 - google-protobuf: 3.21.2 + adm-zip: 0.5.16 + google-protobuf: 3.21.4 https-proxy-agent: 2.2.4 progress: 2.0.3 rimraf: 2.7.1 @@ -15056,9 +15573,9 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.23.4 - '@types/aria-query': 5.0.1 + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 dom-accessibility-api: 0.5.16 @@ -15067,9 +15584,9 @@ snapshots: '@testing-library/dom@9.3.4': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.23.4 - '@types/aria-query': 5.0.1 + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 aria-query: 5.1.3 chalk: 4.1.2 dom-accessibility-api: 0.5.16 @@ -15078,164 +15595,195 @@ snapshots: '@testing-library/jest-dom@6.5.0': dependencies: - '@adobe/css-tools': 4.4.0 - aria-query: 5.3.0 + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 - '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))': + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: - '@babel/runtime': 7.23.4 + '@testing-library/dom': 10.4.0 + + '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@babel/runtime': 7.28.4 '@testing-library/dom': 9.3.4 - '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) - vue: 3.5.13(typescript@5.8.3) + '@vue/test-utils': 2.4.6 + vue: 3.5.26(typescript@5.9.3) optionalDependencies: - '@vue/compiler-sfc': 3.5.13 + '@vue/compiler-sfc': 3.5.26 + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3(supports-color@10.2.2) + fflate: 0.8.2 + token-types: 6.1.1 transitivePeerDependencies: - - '@vue/server-renderer' + - supports-color + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3(supports-color@10.2.2) + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color '@tokenizer/token@0.3.0': {} - '@trysound/sax@0.2.0': {} + '@tootallnate/once@2.0.0': {} - '@tsd/typescript@5.4.5': {} + '@tsd/typescript@5.9.3': {} - '@twemoji/parser@15.0.0': {} - - '@twemoji/parser@15.1.0': {} - - '@twemoji/parser@15.1.1': {} + '@twemoji/parser@16.0.0': {} '@types/accepts@1.3.7': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/archiver@6.0.3': + '@types/archiver@7.0.0': dependencies: - '@types/readdir-glob': 1.1.1 + '@types/readdir-glob': 1.1.5 '@types/argparse@1.0.38': {} - '@types/aria-query@5.0.1': {} + '@types/aria-query@5.0.4': {} - '@types/babel__core@7.20.0': + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - '@types/babel__generator': 7.6.4 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.20.0 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 - '@types/babel__generator@7.6.4': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.28.5 - '@types/babel__template@7.4.1': + '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@types/babel__traverse@7.20.0': + '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.28.5 - '@types/bcryptjs@2.4.6': {} - - '@types/body-parser@1.19.5': + '@types/body-parser@1.19.6': dependencies: - '@types/connect': 3.4.35 - '@types/node': 22.14.0 + '@types/connect': 3.4.38 + '@types/node': 24.10.4 - '@types/braces@3.0.1': {} + '@types/braces@3.0.5': {} '@types/canvas-confetti@1.9.0': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/color-convert@2.0.4': dependencies: - '@types/color-name': 1.1.1 + '@types/color-name': 1.1.5 - '@types/color-name@1.1.1': {} + '@types/color-name@1.1.5': {} - '@types/connect@3.4.35': + '@types/connect@3.4.38': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/connect@3.4.36': - dependencies: - '@types/node': 22.14.0 + '@types/content-disposition@0.5.9': {} - '@types/content-disposition@0.5.8': {} - - '@types/cookie@0.6.0': {} + '@types/cookiejar@2.1.5': {} '@types/debug@4.1.12': dependencies: - '@types/ms': 0.7.34 + '@types/ms': 2.1.0 - '@types/disposable-email-domains@1.0.2': {} + '@types/deep-eql@4.0.2': {} + + '@types/disposable-email-domains@1.0.6': {} '@types/dlv@1.1.5': {} '@types/doctrine@0.0.9': {} + '@types/dom-mediacapture-transform@0.1.11': + dependencies: + '@types/dom-webcodecs': 0.1.13 + + '@types/dom-webcodecs@0.1.13': {} + '@types/eslint@7.29.0': dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 - '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.17.33': + '@types/express-serve-static-core@5.1.0': dependencies: - '@types/node': 22.14.0 - '@types/qs': 6.9.7 - '@types/range-parser': 1.2.4 + '@types/node': 24.10.4 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 - '@types/express@4.17.17': + '@types/express@5.0.4': dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.33 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.1 + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.0 + '@types/serve-static': 1.15.10 - '@types/fluent-ffmpeg@2.1.27': + '@types/fluent-ffmpeg@2.1.28': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/graceful-fs@4.1.6': + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 '@types/hammerjs@2.0.46': {} '@types/hast@3.0.4': dependencies: - '@types/unist': 3.0.2 - - '@types/htmlescape@1.1.3': {} + '@types/unist': 3.0.3 '@types/http-cache-semantics@4.0.4': {} + '@types/http-errors@2.0.5': {} + '@types/http-link-header@1.0.7': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/istanbul-lib-coverage@2.0.4': {} + '@types/insert-text-at-cursor@0.3.2': {} - '@types/istanbul-lib-report@3.0.0': + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': dependencies: - '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports@3.0.1': + '@types/istanbul-reports@3.0.4': dependencies: - '@types/istanbul-lib-report': 3.0.0 + '@types/istanbul-lib-report': 3.0.3 '@types/jest@29.5.12': dependencies: @@ -15249,11 +15797,11 @@ snapshots: '@types/js-yaml@4.0.9': {} - '@types/jsdom@21.1.7': + '@types/jsdom@20.0.1': dependencies: - '@types/node': 22.14.0 - '@types/tough-cookie': 4.0.2 - parse5: 7.2.1 + '@types/node': 24.10.4 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 '@types/json-schema@7.0.15': {} @@ -15261,60 +15809,59 @@ snapshots: '@types/jsonld@1.5.15': {} - '@types/jsrsasign@10.5.15': {} - '@types/long@4.0.2': {} - '@types/matter-js@0.19.8': {} + '@types/matter-js@0.20.2': {} - '@types/mdast@4.0.3': + '@types/mdast@4.0.4': dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 - '@types/mdx@2.0.3': {} + '@types/mdx@2.0.13': {} - '@types/micromatch@4.0.9': + '@types/methods@1.1.4': {} + + '@types/micromatch@4.0.10': dependencies: - '@types/braces': 3.0.1 + '@types/braces': 3.0.5 - '@types/mime-types@2.1.4': {} + '@types/mime-types@3.0.1': {} - '@types/mime@3.0.1': {} + '@types/mime@1.3.5': {} - '@types/minimist@1.2.2': {} + '@types/minimist@1.2.5': {} - '@types/ms@0.7.34': {} + '@types/ms@2.1.0': {} - '@types/mysql@2.15.26': + '@types/mysql@2.15.27': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/node-fetch@2.6.11': + '@types/node-fetch@2.6.13': dependencies: - '@types/node': 22.14.0 - form-data: 4.0.2 + '@types/node': 24.10.4 + form-data: 4.0.5 '@types/node@20.11.17': dependencies: undici-types: 5.26.5 - '@types/node@22.13.10': - dependencies: - undici-types: 6.20.0 - - '@types/node@22.13.15': - dependencies: - undici-types: 6.20.0 - - '@types/node@22.14.0': + '@types/node@20.19.25': dependencies: undici-types: 6.21.0 - '@types/nodemailer@6.4.17': + '@types/node@24.10.4': dependencies: - '@types/node': 22.14.0 + undici-types: 7.16.0 - '@types/normalize-package-data@2.4.1': {} + '@types/nodemailer@7.0.4': + dependencies: + '@aws-sdk/client-sesv2': 3.938.0 + '@types/node': 24.10.4 + transitivePeerDependencies: + - aws-crt + + '@types/normalize-package-data@2.4.4': {} '@types/oauth2orize-pkce@0.1.2': dependencies: @@ -15322,107 +15869,115 @@ snapshots: '@types/oauth2orize@1.11.5': dependencies: - '@types/express': 4.17.17 - '@types/node': 22.14.0 - - '@types/oauth@0.9.6': - dependencies: - '@types/node': 22.14.0 + '@types/express': 5.0.4 + '@types/node': 24.10.4 '@types/offscreencanvas@2019.3.0': {} - '@types/offscreencanvas@2019.7.0': {} + '@types/offscreencanvas@2019.7.3': {} '@types/pg-pool@2.0.6': dependencies: - '@types/pg': 8.11.11 + '@types/pg': 8.16.0 - '@types/pg@8.11.11': + '@types/pg@8.15.6': dependencies: - '@types/node': 22.14.0 - pg-protocol: 1.7.0 - pg-types: 4.0.1 - - '@types/pg@8.6.1': - dependencies: - '@types/node': 22.14.0 - pg-protocol: 1.7.1 + '@types/node': 24.10.4 + pg-protocol: 1.10.3 pg-types: 2.2.0 - '@types/prop-types@15.7.14': {} - - '@types/pug@2.0.10': {} + '@types/pg@8.16.0': + dependencies: + '@types/node': 24.10.4 + pg-protocol: 1.10.3 + pg-types: 2.2.0 '@types/punycode@2.1.4': {} - '@types/qrcode@1.5.5': + '@types/qrcode@1.5.6': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/qs@6.9.7': {} + '@types/qs@6.14.0': {} '@types/random-seed@0.3.5': {} - '@types/range-parser@1.2.4': {} + '@types/range-parser@1.2.7': {} '@types/ratelimiter@3.4.6': {} - '@types/react@18.0.28': + '@types/react@19.2.2': dependencies: - '@types/prop-types': 15.7.14 - '@types/scheduler': 0.23.0 - csstype: 3.1.3 + csstype: 3.2.3 - '@types/readdir-glob@1.1.1': + '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 22.14.0 - - '@types/redis-info@3.0.3': {} + '@types/node': 24.10.4 '@types/rename@1.0.7': {} - '@types/resolve@1.20.3': {} + '@types/resolve@1.20.6': {} - '@types/sanitize-html@2.15.0': + '@types/sanitize-html@2.16.0': dependencies: - htmlparser2: 8.0.1 - - '@types/scheduler@0.23.0': {} + htmlparser2: 8.0.2 '@types/seedrandom@2.4.34': {} '@types/seedrandom@3.0.8': {} - '@types/semver@7.7.0': {} + '@types/semver@7.7.1': {} - '@types/serve-static@1.15.1': + '@types/send@0.17.6': dependencies: - '@types/mime': 3.0.1 - '@types/node': 22.14.0 + '@types/mime': 1.3.5 + '@types/node': 24.10.4 + + '@types/send@1.2.1': + dependencies: + '@types/node': 24.10.4 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.10.4 + '@types/send': 0.17.6 '@types/serviceworker@0.0.74': {} - '@types/shimmer@1.2.0': {} + '@types/simple-oauth2@5.0.8': {} - '@types/simple-oauth2@5.0.7': {} - - '@types/sinon@17.0.3': + '@types/sinon@17.0.4': dependencies: - '@types/sinonjs__fake-timers': 8.1.5 + '@types/sinonjs__fake-timers': 15.0.1 + + '@types/sinonjs__fake-timers@15.0.1': {} '@types/sinonjs__fake-timers@8.1.1': {} - '@types/sinonjs__fake-timers@8.1.5': {} + '@types/sizzle@2.3.10': {} - '@types/sizzle@2.3.3': {} + '@types/stack-utils@2.0.3': {} - '@types/stack-utils@2.0.1': {} + '@types/statuses@2.0.6': {} - '@types/statuses@2.0.4': {} + '@types/superagent@8.1.9': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 24.10.4 + form-data: 4.0.5 + + '@types/supertest@6.0.3': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 '@types/tedious@4.0.14': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 + + '@types/textarea-caret@3.0.4': {} '@types/throttle-debounce@5.0.2': {} @@ -15430,154 +15985,108 @@ snapshots: '@types/tmp@0.2.6': {} - '@types/tough-cookie@4.0.2': {} - '@types/tough-cookie@4.0.5': {} - '@types/unist@3.0.2': {} + '@types/unist@3.0.3': {} '@types/uuid@9.0.8': {} '@types/vary@1.1.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 + + '@types/wawoff2@1.0.2': + dependencies: + '@types/node': 24.10.4 '@types/web-push@3.6.4': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 + + '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 - '@types/yargs-parser@21.0.0': {} + '@types/yargs-parser@21.0.3': {} - '@types/yargs@17.0.19': + '@types/yargs@17.0.34': dependencies: - '@types/yargs-parser': 21.0.0 + '@types/yargs-parser': 21.0.3 - '@types/yauzl@2.10.0': + '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 optional: true - '@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.18.1(eslint@9.22.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.18.1(eslint@9.39.2)(typescript@5.3.3) '@typescript-eslint/scope-manager': 6.18.1 - '@typescript-eslint/type-utils': 6.18.1(eslint@9.22.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.18.1(eslint@9.22.0)(typescript@5.3.3) + '@typescript-eslint/type-utils': 6.18.1(eslint@9.39.2)(typescript@5.3.3) + '@typescript-eslint/utils': 6.18.1(eslint@9.39.2)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.18.1 debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 + eslint: 9.39.2 graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 5.3.2 natural-compare: 1.4.0 - semver: 7.7.1 + semver: 7.7.3 ts-api-utils: 1.3.0(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)': + '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/scope-manager': 8.26.0 - '@typescript-eslint/type-utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.26.0 - eslint: 9.22.0 - graphemer: 1.4.0 - ignore: 5.3.1 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/type-utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.1 + eslint: 9.39.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.29.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/type-utils': 8.29.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/utils': 8.29.0(eslint@9.22.0)(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.29.0 - eslint: 9.22.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0)(typescript@5.8.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.29.1 - '@typescript-eslint/type-utils': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.29.1 - eslint: 9.22.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3)': + '@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 6.18.1 '@typescript-eslint/types': 6.18.1 '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.18.1 debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 + eslint: 9.39.2 optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2)': + '@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.26.0 - '@typescript-eslint/types': 8.26.0 - '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - typescript: 5.8.2 + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.1 + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.29.0(eslint@9.22.0)(typescript@5.8.2)': + '@typescript-eslint/project-service@8.50.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.29.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.29.1 - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.29.1 - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 + debug: 4.4.3(supports-color@10.2.2) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -15586,200 +16095,97 @@ snapshots: '@typescript-eslint/types': 6.18.1 '@typescript-eslint/visitor-keys': 6.18.1 - '@typescript-eslint/scope-manager@8.26.0': + '@typescript-eslint/scope-manager@8.50.1': dependencies: - '@typescript-eslint/types': 8.26.0 - '@typescript-eslint/visitor-keys': 8.26.0 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/visitor-keys': 8.50.1 - '@typescript-eslint/scope-manager@8.29.0': + '@typescript-eslint/tsconfig-utils@8.50.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/visitor-keys': 8.29.0 + typescript: 5.9.3 - '@typescript-eslint/scope-manager@8.29.1': - dependencies: - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/visitor-keys': 8.29.1 - - '@typescript-eslint/type-utils@6.18.1(eslint@9.22.0)(typescript@5.3.3)': + '@typescript-eslint/type-utils@6.18.1(eslint@9.39.2)(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) - '@typescript-eslint/utils': 6.18.1(eslint@9.22.0)(typescript@5.3.3) - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 + '@typescript-eslint/utils': 6.18.1(eslint@9.39.2)(typescript@5.3.3) + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.2 ts-api-utils: 1.3.0(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.26.0(eslint@9.22.0)(typescript@5.8.2)': + '@typescript-eslint/type-utils@8.50.1(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) - '@typescript-eslint/utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@8.29.0(eslint@9.22.0)(typescript@5.8.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) - '@typescript-eslint/utils': 8.29.0(eslint@9.22.0)(typescript@5.8.2) - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@8.29.1(eslint@9.22.0)(typescript@5.8.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@6.18.1': {} - '@typescript-eslint/types@8.26.0': {} + '@typescript-eslint/types@8.50.1': {} - '@typescript-eslint/types@8.29.0': {} - - '@typescript-eslint/types@8.29.1': {} + '@typescript-eslint/types@8.51.0': {} '@typescript-eslint/typescript-estree@6.18.1(typescript@5.3.3)': dependencies: '@typescript-eslint/types': 6.18.1 '@typescript-eslint/visitor-keys': 6.18.1 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.7.1 + semver: 7.7.3 ts-api-utils: 1.3.0(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.26.0(typescript@5.8.2)': + '@typescript-eslint/typescript-estree@8.50.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.26.0 - '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.0(supports-color@5.5.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 + '@typescript-eslint/project-service': 8.50.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/visitor-keys': 8.50.1 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.4 - semver: 7.6.3 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)': + '@typescript-eslint/utils@6.18.1(eslint@9.39.2)(typescript@5.3.3)': dependencies: - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/visitor-keys': 8.29.0 - debug: 4.4.0(supports-color@5.5.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.3 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.29.1(typescript@5.8.2)': - dependencies: - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/visitor-keys': 8.29.1 - debug: 4.4.0(supports-color@5.5.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.29.1(typescript@5.8.3)': - dependencies: - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/visitor-keys': 8.29.1 - debug: 4.4.0(supports-color@5.5.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@6.18.1(eslint@9.22.0)(typescript@5.3.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.39.2) '@types/json-schema': 7.0.15 - '@types/semver': 7.7.0 + '@types/semver': 7.7.1 '@typescript-eslint/scope-manager': 6.18.1 '@typescript-eslint/types': 6.18.1 '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) - eslint: 9.22.0 - semver: 7.7.1 + eslint: 9.39.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.26.0(eslint@9.22.0)(typescript@5.8.2)': + '@typescript-eslint/utils@8.50.1(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - '@typescript-eslint/scope-manager': 8.26.0 - '@typescript-eslint/types': 8.26.0 - '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) - eslint: 9.22.0 - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.29.0(eslint@9.22.0)(typescript@5.8.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) - eslint: 9.22.0 - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.29.1(eslint@9.22.0)(typescript@5.8.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - '@typescript-eslint/scope-manager': 8.29.1 - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.2) - eslint: 9.22.0 - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.29.1(eslint@9.22.0)(typescript@5.8.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - '@typescript-eslint/scope-manager': 8.29.1 - '@typescript-eslint/types': 8.29.1 - '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.3) - eslint: 9.22.0 - typescript: 5.8.3 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -15788,43 +16194,64 @@ snapshots: '@typescript-eslint/types': 6.18.1 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.26.0': + '@typescript-eslint/visitor-keys@8.50.1': dependencies: - '@typescript-eslint/types': 8.26.0 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.50.1 + eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.29.0': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-darwin-x64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-linux-arm64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-linux-arm@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-linux-x64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview-win32-x64@7.0.0-dev.20251226.1': + optional: true + + '@typescript/native-preview@7.0.0-dev.20251226.1': + optionalDependencies: + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20251226.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20251226.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20251226.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20251226.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20251226.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20251226.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20251226.1 + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))': dependencies: - '@typescript-eslint/types': 8.29.0 - eslint-visitor-keys: 4.2.0 + '@rolldown/pluginutils': 1.0.0-beta.53 + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + vue: 3.5.26(typescript@5.9.3) - '@typescript-eslint/visitor-keys@8.29.1': + '@vitest/coverage-v8@4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': dependencies: - '@typescript-eslint/types': 8.29.1 - eslint-visitor-keys: 4.2.0 - - '@ungap/structured-clone@1.2.0': {} - - '@vitejs/plugin-vue@5.2.3(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.3))': - dependencies: - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - vue: 3.5.13(typescript@5.8.3) - - '@vitest/coverage-v8@3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))': - dependencies: - '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.0(supports-color@5.5.0) + '@vitest/utils': 4.0.16 + ast-v8-to-istanbul: 0.3.8 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 - magicast: 0.3.5 - std-env: 3.9.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + istanbul-reports: 3.2.0 + magicast: 0.5.1 + obug: 2.1.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) transitivePeerDependencies: - supports-color @@ -15832,266 +16259,325 @@ snapshots: dependencies: '@vitest/spy': 2.0.5 '@vitest/utils': 2.0.5 - chai: 5.2.0 + chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/expect@3.1.1': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 3.1.1 - '@vitest/utils': 3.1.1 - chai: 5.2.0 + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.1(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3))': + '@vitest/expect@4.0.16': dependencies: - '@vitest/spy': 3.1.1 + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + chai: 6.2.1 + tinyrainbow: 3.0.3 + + '@vitest/mocker@3.2.4(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.21 optionalDependencies: - msw: 2.7.3(@types/node@22.14.0)(typescript@5.8.3) - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + msw: 2.12.6(@types/node@24.10.4)(typescript@5.9.3) + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + + '@vitest/mocker@4.0.16(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.6(@types/node@24.10.4)(typescript@5.9.3) + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) '@vitest/pretty-format@2.0.5': dependencies: tinyrainbow: 1.2.0 - '@vitest/pretty-format@2.1.1': + '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 - '@vitest/pretty-format@3.1.1': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.1.1': + '@vitest/pretty-format@4.0.16': dependencies: - '@vitest/utils': 3.1.1 + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.16': + dependencies: + '@vitest/utils': 4.0.16 pathe: 2.0.3 - '@vitest/snapshot@3.1.1': + '@vitest/snapshot@4.0.16': dependencies: - '@vitest/pretty-format': 3.1.1 - magic-string: 0.30.17 + '@vitest/pretty-format': 4.0.16 + magic-string: 0.30.21 pathe: 2.0.3 '@vitest/spy@2.0.5': dependencies: tinyspy: 3.0.2 - '@vitest/spy@3.1.1': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.4 + + '@vitest/spy@4.0.16': {} '@vitest/utils@2.0.5': dependencies: '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - loupe: 3.1.3 + loupe: 3.2.1 tinyrainbow: 1.2.0 - '@vitest/utils@2.1.1': + '@vitest/utils@2.1.9': dependencies: - '@vitest/pretty-format': 2.1.1 - loupe: 3.1.3 + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 tinyrainbow: 1.2.0 - '@vitest/utils@3.1.1': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.1 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 tinyrainbow: 2.0.0 - '@volar/language-core@2.2.0': + '@vitest/utils@4.0.16': dependencies: - '@volar/source-map': 2.2.0 + '@vitest/pretty-format': 4.0.16 + tinyrainbow: 3.0.3 - '@volar/language-core@2.4.11': + '@volar/language-core@2.4.15': dependencies: - '@volar/source-map': 2.4.11 + '@volar/source-map': 2.4.15 - '@volar/source-map@2.2.0': + '@volar/language-core@2.4.27': dependencies: - muggle-string: 0.4.1 + '@volar/source-map': 2.4.27 - '@volar/source-map@2.4.11': {} + '@volar/source-map@2.4.15': {} - '@volar/typescript@2.2.0': + '@volar/source-map@2.4.27': {} + + '@volar/typescript@2.4.15': dependencies: - '@volar/language-core': 2.2.0 + '@volar/language-core': 2.4.15 path-browserify: 1.0.1 + vscode-uri: 3.1.0 - '@volar/typescript@2.4.11': + '@volar/typescript@2.4.27': dependencies: - '@volar/language-core': 2.4.11 + '@volar/language-core': 2.4.27 path-browserify: 1.0.1 - vscode-uri: 3.0.8 + vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.13': + '@vue/compiler-core@3.5.26': dependencies: - '@babel/parser': 7.25.6 - '@vue/shared': 3.5.13 - entities: 4.5.0 + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.26 + entities: 7.0.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.13': + '@vue/compiler-dom@3.5.26': dependencies: - '@vue/compiler-core': 3.5.13 - '@vue/shared': 3.5.13 + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 - '@vue/compiler-sfc@3.5.13': + '@vue/compiler-sfc@3.5.26': dependencies: - '@babel/parser': 7.25.6 - '@vue/compiler-core': 3.5.13 - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 estree-walker: 2.0.2 - magic-string: 0.30.17 - postcss: 8.5.3 + magic-string: 0.30.21 + postcss: 8.5.6 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.13': + '@vue/compiler-ssr@3.5.26': dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/shared': 3.5.13 + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 he: 1.2.0 - '@vue/language-core@2.0.16(typescript@5.8.3)': + '@vue/language-core@2.2.12(typescript@5.9.3)': dependencies: - '@volar/language-core': 2.2.0 - '@vue/compiler-dom': 3.5.13 - '@vue/shared': 3.5.13 - computeds: 0.0.1 - minimatch: 9.0.4 - path-browserify: 1.0.1 - vue-template-compiler: 2.7.14 - optionalDependencies: - typescript: 5.8.3 - - '@vue/language-core@2.2.8(typescript@5.8.3)': - dependencies: - '@volar/language-core': 2.4.11 - '@vue/compiler-dom': 3.5.13 + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.26 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.13 - alien-signals: 1.0.3 + '@vue/shared': 3.5.26 + alien-signals: 1.0.13 minimatch: 9.0.4 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 - '@vue/reactivity@3.5.13': + '@vue/language-core@3.2.1': dependencies: - '@vue/shared': 3.5.13 + '@volar/language-core': 2.4.27 + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + alien-signals: 3.1.2 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 - '@vue/runtime-core@3.5.13': + '@vue/reactivity@3.5.26': dependencies: - '@vue/reactivity': 3.5.13 - '@vue/shared': 3.5.13 + '@vue/shared': 3.5.26 - '@vue/runtime-dom@3.5.13': + '@vue/runtime-core@3.5.26': dependencies: - '@vue/reactivity': 3.5.13 - '@vue/runtime-core': 3.5.13 - '@vue/shared': 3.5.13 - csstype: 3.1.3 + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 - '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': + '@vue/runtime-dom@3.5.26': dependencies: - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 - vue: 3.5.13(typescript@5.8.3) + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 - '@vue/shared@3.5.13': {} - - '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))': + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': dependencies: - js-beautify: 1.14.9 - vue: 3.5.13(typescript@5.8.3) - vue-component-type-helpers: 1.8.4 - optionalDependencies: - '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.3)) + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.9.3) + + '@vue/shared@3.5.26': {} + + '@vue/test-utils@2.4.6': + dependencies: + js-beautify: 1.15.4 + vue-component-type-helpers: 2.2.12 '@webgpu/types@0.1.38': {} - '@xhmikosr/archive-type@7.0.0': + '@xhmikosr/archive-type@7.1.0': dependencies: - file-type: 19.6.0 + file-type: 20.5.0 + transitivePeerDependencies: + - supports-color - '@xhmikosr/bin-check@7.0.3': + '@xhmikosr/bin-check@7.1.0': dependencies: execa: 5.1.1 isexe: 2.0.0 - '@xhmikosr/bin-wrapper@13.0.5': + '@xhmikosr/bin-wrapper@13.2.0': dependencies: - '@xhmikosr/bin-check': 7.0.3 - '@xhmikosr/downloader': 15.0.1 + '@xhmikosr/bin-check': 7.1.0 + '@xhmikosr/downloader': 15.2.0 '@xhmikosr/os-filter-obj': 3.0.0 bin-version-check: 5.1.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color - '@xhmikosr/decompress-tar@8.0.1': + '@xhmikosr/decompress-tar@8.1.0': dependencies: - file-type: 19.6.0 + file-type: 20.5.0 is-stream: 2.0.1 tar-stream: 3.1.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color - '@xhmikosr/decompress-tarbz2@8.0.2': + '@xhmikosr/decompress-tarbz2@8.1.0': dependencies: - '@xhmikosr/decompress-tar': 8.0.1 - file-type: 19.6.0 + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 is-stream: 2.0.1 seek-bzip: 2.0.0 unbzip2-stream: 1.4.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color - '@xhmikosr/decompress-targz@8.0.1': + '@xhmikosr/decompress-targz@8.1.0': dependencies: - '@xhmikosr/decompress-tar': 8.0.1 - file-type: 19.6.0 + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 is-stream: 2.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color - '@xhmikosr/decompress-unzip@7.0.0': + '@xhmikosr/decompress-unzip@7.1.0': dependencies: - file-type: 19.6.0 + file-type: 20.5.0 get-stream: 6.0.1 yauzl: 3.2.0 + transitivePeerDependencies: + - supports-color - '@xhmikosr/decompress@10.0.1': + '@xhmikosr/decompress@10.2.0': dependencies: - '@xhmikosr/decompress-tar': 8.0.1 - '@xhmikosr/decompress-tarbz2': 8.0.2 - '@xhmikosr/decompress-targz': 8.0.1 - '@xhmikosr/decompress-unzip': 7.0.0 + '@xhmikosr/decompress-tar': 8.1.0 + '@xhmikosr/decompress-tarbz2': 8.1.0 + '@xhmikosr/decompress-targz': 8.1.0 + '@xhmikosr/decompress-unzip': 7.1.0 graceful-fs: 4.2.11 - make-dir: 4.0.0 strip-dirs: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color - '@xhmikosr/downloader@15.0.1': + '@xhmikosr/downloader@15.2.0': dependencies: - '@xhmikosr/archive-type': 7.0.0 - '@xhmikosr/decompress': 10.0.1 + '@xhmikosr/archive-type': 7.1.0 + '@xhmikosr/decompress': 10.2.0 content-disposition: 0.5.4 - defaults: 3.0.0 + defaults: 2.0.2 ext-name: 5.0.0 - file-type: 19.6.0 + file-type: 20.5.0 filenamify: 6.0.0 get-stream: 6.0.1 got: 13.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color '@xhmikosr/os-filter-obj@3.0.0': dependencies: arch: 3.0.0 - abbrev@1.1.1: {} + '@xmldom/xmldom@0.9.8': + optional: true + + abab@2.0.6: {} + + abbrev@1.1.1: + optional: true abbrev@2.0.0: {} + abbrev@4.0.0: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -16103,19 +16589,33 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-attributes@1.9.5(acorn@8.14.1): + accepts@2.0.0: dependencies: - acorn: 8.14.1 + mime-types: 3.0.2 + negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.14.1): + acorn-globals@7.0.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 acorn@7.4.1: {} - acorn@8.14.1: {} + acorn@8.15.0: {} - adm-zip@0.5.10: + adm-zip@0.5.16: optional: true agent-base@4.3.0: @@ -16125,30 +16625,18 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - optional: true - - agent-base@7.1.0: - dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - agent-base@7.1.3: {} + agent-base@7.1.4: {} aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - aggregate-error@5.0.0: - dependencies: - clean-stack: 5.2.0 - indent-string: 5.0.0 - - aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7: + aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/1dc7f60cda78d030dadfc518a33c472202b2ef67: dependencies: vscode-languageclient: 9.0.1 @@ -16192,22 +16680,24 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.1 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alien-signals@1.0.3: {} + alien-signals@1.0.13: {} - analytics-utils@1.0.14(@types/dlv@1.1.5): + alien-signals@3.1.2: {} + + analytics-utils@1.1.1(@types/dlv@1.1.5): dependencies: - '@analytics/type-utils': 0.6.2 + '@analytics/type-utils': 0.6.4 '@types/dlv': 1.1.5 dlv: 1.1.3 - analytics@0.8.16(@types/dlv@1.1.5): + analytics@0.8.19(@types/dlv@1.1.5): dependencies: - '@analytics/core': 0.12.17(@types/dlv@1.1.5) - '@analytics/storage-utils': 0.4.2 + '@analytics/core': 0.13.2(@types/dlv@1.1.5) + '@analytics/storage-utils': 0.4.4 transitivePeerDependencies: - '@types/dlv' @@ -16219,11 +16709,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: @@ -16231,11 +16717,9 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} - ansis@3.17.0: {} - - any-promise@1.3.0: {} + ansis@4.2.0: {} anymatch@3.1.3: dependencies: @@ -16246,7 +16730,7 @@ snapshots: append-field@1.0.0: {} - aproba@2.0.0: + aproba@2.1.0: optional: true arch@2.2.0: {} @@ -16255,23 +16739,26 @@ snapshots: archiver-utils@5.0.2: dependencies: - glob: 10.3.10 + glob: 10.5.0 graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 lodash: 4.17.21 normalize-path: 3.0.0 - readable-stream: 4.3.0 + readable-stream: 4.7.0 archiver@7.0.1: dependencies: archiver-utils: 5.0.2 - async: 3.2.4 + async: 3.2.6 buffer-crc32: 1.0.0 - readable-stream: 4.3.0 - readdir-glob: 1.1.2 - tar-stream: 3.1.6 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a are-we-there-yet@2.0.0: dependencies: @@ -16289,77 +16776,67 @@ snapshots: aria-query@5.1.3: dependencies: - deep-equal: 2.2.0 + deep-equal: 2.2.3 aria-query@5.3.0: dependencies: dequal: 2.0.3 - array-buffer-byte-length@1.0.0: - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.2 + aria-query@5.3.2: {} - array-buffer-byte-length@1.0.1: + array-buffer-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 + call-bound: 1.0.4 + is-array-buffer: 3.0.5 array-flatten@1.1.1: {} - array-includes@3.1.8: + array-includes@3.1.9: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 array-union@2.1.0: {} - array.prototype.findlastindex@1.2.5: + array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 - array.prototype.flat@1.3.2: + array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - - array.prototype.flatmap@1.3.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - - arraybuffer.prototype.slice@1.0.1: - dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.7 - define-properties: 1.2.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 - - arraybuffer.prototype.slice@1.0.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 arrify@1.0.1: {} @@ -16367,7 +16844,7 @@ snapshots: asn1.js@5.4.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.2 inherits: 2.0.4 minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 @@ -16376,13 +16853,13 @@ snapshots: dependencies: safer-buffer: 2.1.2 - asn1js@3.0.5: + asn1js@3.0.6: dependencies: - pvtsutils: 1.3.5 - pvutils: 1.1.3 + pvtsutils: 1.3.6 + pvutils: 1.1.5 tslib: 2.8.1 - assert-never@1.2.1: {} + assert-never@1.4.0: {} assert-plus@1.0.0: {} @@ -16392,17 +16869,25 @@ snapshots: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@0.3.8: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + astral-regex@2.0.0: {} astring@1.9.0: {} + async-function@1.0.0: {} + async-mutex@0.5.0: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 async@0.2.10: {} - async@3.2.4: {} + async@3.2.6: {} asynckit@0.4.0: {} @@ -16410,58 +16895,48 @@ snapshots: atomic-sleep@1.0.0: {} - available-typed-arrays@1.0.5: {} - available-typed-arrays@1.0.7: dependencies: - possible-typed-array-names: 1.0.0 + possible-typed-array-names: 1.1.0 - avvio@9.0.0: + avvio@9.1.0: dependencies: - '@fastify/error': 4.0.0 - fastq: 1.17.1 + '@fastify/error': 4.2.0 + fastq: 1.19.1 aws-sdk-client-mock@4.1.0: dependencies: - '@types/sinon': 17.0.3 + '@types/sinon': 17.0.4 sinon: 18.0.1 tslib: 2.8.1 aws-sign2@0.7.0: {} - aws4@1.12.0: {} + aws4@1.13.2: {} axios@0.24.0: dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.11(debug@4.4.3) transitivePeerDependencies: - debug - axios@1.7.9(debug@4.4.0): + axios@1.13.2(debug@4.4.3): dependencies: - follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.2 + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - axios@1.8.4(debug@4.4.0): - dependencies: - follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.2 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug + b4a@1.7.3: {} - b4a@1.6.4: {} - - babel-jest@29.7.0(@babel/core@7.24.7): + babel-jest@29.7.0(@babel/core@7.28.5): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.0 + '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.24.7) + babel-preset-jest: 29.6.3(@babel/core@7.28.5) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -16470,7 +16945,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.27.1 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -16480,73 +16955,65 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.25.6 - '@types/babel__core': 7.20.0 - '@types/babel__traverse': 7.20.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 - babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.5): + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): dependencies: - '@babel/core': 7.23.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.5) + '@babel/core': 7.28.5 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7): + babel-preset-jest@29.6.3(@babel/core@7.28.5): dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) - - babel-preset-jest@29.6.3(@babel/core@7.24.7): - dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) babel-walk@3.0.0-canary-5: dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.28.5 bail@2.0.2: {} balanced-match@1.0.2: {} + bare-events@2.8.1: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.8.29: {} + bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 - bcryptjs@2.4.3: {} + bcryptjs@3.0.3: {} - better-opn@3.0.2: + bidi-js@1.0.3: dependencies: - open: 8.4.2 + require-from-string: 2.0.2 + optional: true bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 - semver: 7.6.3 + semver: 7.7.3 semver-truncate: 3.0.0 bin-version@6.0.0: @@ -16554,20 +17021,13 @@ snapshots: execa: 5.1.1 find-versions: 5.1.0 - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - optional: true - blob-util@2.0.2: {} bluebird@3.7.2: {} blurhash@2.0.5: {} - bn.js@4.12.0: {} + bn.js@4.12.2: {} body-parser@1.20.3: dependencies: @@ -16586,16 +17046,30 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.1: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3(supports-color@10.2.2) + http-errors: 2.0.0 + iconv-lite: 0.7.0 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.1 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} - bowser@2.11.0: {} + bowser@2.12.1: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -16603,21 +17077,20 @@ snapshots: dependencies: fill-range: 7.1.1 - broadcast-channel@7.1.0: + broadcast-channel@7.2.0: dependencies: - '@babel/runtime': 7.27.0 - oblivious-set: 1.4.0 + '@babel/runtime': 7.28.4 + oblivious-set: 2.0.0 p-queue: 6.6.2 unload: 2.4.1 - browser-assert@1.2.1: {} - - browserslist@4.24.4: + browserslist@4.28.0: dependencies: - caniuse-lite: 1.0.30001695 - electron-to-chromium: 1.5.83 - node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + baseline-browser-mapping: 2.8.29 + caniuse-lite: 1.0.30001755 + electron-to-chromium: 1.5.255 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) bs-logger@0.2.6: dependencies: @@ -16650,47 +17123,50 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bufferutil@4.0.9: + bufferutil@4.1.0: dependencies: - node-gyp-build: 4.6.0 + node-gyp-build: 4.8.4 optional: true - bullmq@5.48.1: + bullmq@5.66.3: dependencies: cron-parser: 4.9.0 - ioredis: 5.6.0 - msgpackr: 1.11.2 + ioredis: 5.8.2 + msgpackr: 1.11.5 node-abort-controller: 3.1.1 - semver: 7.7.1 + semver: 7.7.3 tslib: 2.8.1 - uuid: 9.0.1 + uuid: 11.1.0 transitivePeerDependencies: - supports-color + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + buraha@0.0.1: {} busboy@1.6.0: dependencies: streamsearch: 1.1.0 + byte-counter@0.1.0: {} + bytes@3.1.2: {} - cac@6.7.14: {} - - cacache@18.0.0: + cacache@20.0.3: dependencies: - '@npmcli/fs': 3.1.0 + '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 10.3.10 - lru-cache: 10.4.3 + glob: 13.0.0 + lru-cache: 11.2.4 minipass: 7.1.2 - minipass-collect: 1.0.2 + minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - p-map: 4.0.0 - ssri: 10.0.4 - tar: 6.2.1 - unique-filename: 3.0.0 + p-map: 7.0.3 + ssri: 13.0.0 + unique-filename: 5.0.0 cacheable-lookup@7.0.0: {} @@ -16698,35 +17174,34 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 get-stream: 6.0.1 - http-cache-semantics: 4.1.1 + http-cache-semantics: 4.2.0 keyv: 4.5.4 mimic-response: 4.0.0 - normalize-url: 8.0.1 + normalize-url: 8.1.0 responselike: 3.0.0 - cacheable-request@12.0.1: + cacheable-request@13.0.15: dependencies: '@types/http-cache-semantics': 4.0.4 get-stream: 9.0.1 - http-cache-semantics: 4.1.1 - keyv: 4.5.4 + http-cache-semantics: 4.2.0 + keyv: 5.5.4 mimic-response: 4.0.0 - normalize-url: 8.0.1 - responselike: 3.0.0 + normalize-url: 8.1.0 + responselike: 4.0.2 - cachedir@2.3.0: {} + cachedir@2.4.0: {} call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.7: + call-bind@1.0.8: dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 call-bound@1.0.4: @@ -16734,8 +17209,6 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - call-me-maybe@1.0.2: {} - callsites@3.1.0: {} camelcase-keys@6.2.2: @@ -16750,50 +17223,38 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.24.4 - caniuse-lite: 1.0.30001591 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001755 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001591: {} + caniuse-lite@1.0.30001755: {} - caniuse-lite@1.0.30001695: {} + canonicalize@2.1.0: {} - canonicalize@1.0.8: {} - - canvas-confetti@1.9.3: {} - - canvas@3.1.0: - dependencies: - node-addon-api: 7.1.1 - prebuild-install: 7.1.3 - optional: true + canvas-confetti@1.9.4: {} caseless@0.12.0: {} - cbor@9.0.2: + cbor@10.0.11: dependencies: nofilter: 3.1.0 ccount@2.0.1: {} - chai@5.2.0: + chai@5.3.3: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 - pathval: 2.0.0 + loupe: 3.2.1 + pathval: 2.0.1 - chalk-template@1.1.0: - dependencies: - chalk: 5.4.1 + chai@6.2.1: {} - chalk@2.4.2: + chalk-template@1.1.2: dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + chalk: 5.6.2 chalk@3.0.0: dependencies: @@ -16805,7 +17266,9 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.4.1: {} + chalk@5.6.2: {} + + change-case@5.4.4: {} char-regex@1.0.2: {} @@ -16817,29 +17280,29 @@ snapshots: character-parser@2.2.0: dependencies: - is-regex: 1.1.4 + is-regex: 1.2.1 - chart.js@4.4.8: + chart.js@4.5.1: dependencies: - '@kurkle/color': 0.3.2 + '@kurkle/color': 0.3.4 - chartjs-adapter-date-fns@3.0.0(chart.js@4.4.8)(date-fns@4.1.0): + chartjs-adapter-date-fns@3.0.0(chart.js@4.5.1)(date-fns@4.1.0): dependencies: - chart.js: 4.4.8 + chart.js: 4.5.1 date-fns: 4.1.0 - chartjs-chart-matrix@2.1.1(chart.js@4.4.8): + chartjs-chart-matrix@3.0.0(chart.js@4.5.1): dependencies: - chart.js: 4.4.8 + chart.js: 4.5.1 - chartjs-plugin-gradient@0.6.1(chart.js@4.4.8): + chartjs-plugin-gradient@0.6.1(chart.js@4.5.1): dependencies: - chart.js: 4.4.8 + chart.js: 4.5.1 - chartjs-plugin-zoom@2.2.0(chart.js@4.4.8): + chartjs-plugin-zoom@2.2.0(chart.js@4.5.1): dependencies: '@types/hammerjs': 2.0.46 - chart.js: 4.4.8 + chart.js: 4.5.1 hammerjs: 2.0.8 check-error@2.1.1: {} @@ -16849,75 +17312,68 @@ snapshots: cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 - css-select: 5.1.0 - css-what: 6.1.0 + css-select: 5.2.2 + css-what: 6.2.2 domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 cheerio@1.0.0: dependencies: cheerio-select: 2.1.0 dom-serializer: 2.0.0 domhandler: 5.0.3 - domutils: 3.1.0 - encoding-sniffer: 0.2.0 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 htmlparser2: 9.1.0 - parse5: 7.2.1 - parse5-htmlparser2-tree-adapter: 7.0.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 6.19.8 + undici: 6.22.0 whatwg-mimetype: 4.0.0 - chokidar@4.0.3: + cheerio@1.1.2: dependencies: - readdirp: 4.1.2 + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 + htmlparser2: 10.0.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 7.16.0 + whatwg-mimetype: 4.0.0 - chownr@1.1.4: + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chownr@2.0.0: optional: true - chownr@2.0.0: {} - chownr@3.0.0: {} - chromatic@11.28.0: {} + chromatic@13.3.4: {} - ci-info@3.7.1: {} + ci-info@3.9.0: {} - ci-info@4.1.0: {} + ci-info@4.3.1: {} - cjs-module-lexer@1.2.2: {} + cjs-module-lexer@1.4.3: {} clean-stack@2.2.0: {} - clean-stack@5.2.0: - dependencies: - escape-string-regexp: 5.0.0 - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 - cli-highlight@2.1.11: - dependencies: - chalk: 4.1.2 - highlight.js: 10.7.3 - mz: 2.7.0 - parse5: 5.1.1 - parse5-htmlparser2-tree-adapter: 6.0.1 - yargs: 16.2.0 - - cli-table3@0.6.3: + cli-table3@0.6.1: dependencies: string-width: 4.2.3 optionalDependencies: - '@colors/colors': 1.5.0 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 + colors: 1.4.0 cli-truncate@2.1.0: dependencies: @@ -16944,28 +17400,34 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + cluster-key-slot@1.1.2: {} co@4.6.0: {} - collect-v8-coverage@1.0.1: {} - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 + collect-v8-coverage@1.0.3: {} color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 color-name@1.1.4: {} + color-name@2.1.0: {} + color-string@1.9.1: dependencies: color-name: 1.1.4 - simple-swizzle: 0.2.2 + simple-swizzle: 0.2.4 color-support@1.1.3: optional: true @@ -16977,7 +17439,12 @@ snapshots: colord@2.9.3: {} - colorette@2.0.19: {} + colorette@1.4.0: {} + + colorette@2.0.20: {} + + colors@1.4.0: + optional: true combined-stream@1.0.8: dependencies: @@ -16987,14 +17454,14 @@ snapshots: commander@10.0.1: {} + commander@11.1.0: {} + commander@12.1.0: {} commander@2.20.3: {} commander@6.2.1: {} - commander@7.2.0: {} - commander@8.3.0: {} commander@9.5.0: {} @@ -17003,23 +17470,23 @@ snapshots: compare-versions@6.1.1: {} + component-emitter@1.3.1: {} + compress-commons@6.0.2: dependencies: crc-32: 1.2.2 crc32-stream: 6.0.0 is-stream: 2.0.1 normalize-path: 3.0.0 - readable-stream: 4.3.0 - - computeds@0.0.1: {} + readable-stream: 4.7.0 concat-map@0.0.1: {} - concat-stream@1.6.2: + concat-stream@2.0.0: dependencies: buffer-from: 1.1.2 inherits: 2.0.4 - readable-stream: 2.3.7 + readable-stream: 3.6.2 typedarray: 0.0.6 config-chain@1.1.13: @@ -17027,33 +17494,35 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - consola@3.4.0: {} + consola@3.4.2: {} console-control-strings@1.1.0: optional: true constantinople@4.0.1: dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.1: {} + content-type@1.0.5: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} - cookie@0.6.0: {} + cookie-signature@1.2.2: {} cookie@0.7.1: {} - cookie@0.7.2: {} + cookie@1.0.2: {} - cookie@1.0.1: {} + cookiejar@2.1.4: {} core-js@3.29.1: {} @@ -17071,7 +17540,7 @@ snapshots: crc32-stream@6.0.0: dependencies: crc-32: 1.2.2 - readable-stream: 4.3.0 + readable-stream: 4.7.0 create-jest@29.7.0(@types/node@20.11.17): dependencies: @@ -17088,28 +17557,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.13.15): + create-jest@29.7.0(@types/node@24.10.4): 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.13.15) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-jest@29.7.0(@types/node@22.14.0): - 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.14.0) + jest-config: 29.7.0(@types/node@24.10.4) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17120,34 +17574,17 @@ snapshots: cron-parser@4.9.0: dependencies: - luxon: 3.3.0 + luxon: 3.7.2 - cropperjs@2.0.0: + cropperjs@2.1.0: dependencies: - '@cropper/elements': 2.0.0 - '@cropper/utils': 2.0.0 + '@cropper/elements': 2.1.0 + '@cropper/utils': 2.1.0 - cross-env@7.0.3: + cross-env@10.1.0: dependencies: - cross-spawn: 7.0.3 - - cross-fetch@3.1.6(encoding@0.1.13): - dependencies: - node-fetch: 2.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - cross-fetch@4.1.0(encoding@0.1.13): - dependencies: - node-fetch: 2.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 cross-spawn@7.0.6: dependencies: @@ -17155,16 +17592,16 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@7.2.0(postcss@8.5.3): + css-declaration-sorter@7.3.0(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - css-select@5.1.0: + css-select@5.2.2: dependencies: boolbase: 1.0.0 - css-what: 6.1.0 + css-what: 6.2.2 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 nth-check: 2.1.1 css-tree@2.2.1: @@ -17172,103 +17609,113 @@ snapshots: mdn-data: 2.0.28 source-map-js: 1.2.1 - css-tree@2.3.1: + css-tree@3.1.0: dependencies: - mdn-data: 2.0.30 + mdn-data: 2.12.2 source-map-js: 1.2.1 - css-what@6.1.0: {} + css-what@6.2.2: {} css.escape@1.5.1: {} cssesc@3.0.0: {} - cssnano-preset-default@7.0.6(postcss@8.5.3): + cssnano-preset-default@7.0.10(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - css-declaration-sorter: 7.2.0(postcss@8.5.3) - cssnano-utils: 5.0.0(postcss@8.5.3) - postcss: 8.5.3 - postcss-calc: 10.1.0(postcss@8.5.3) - postcss-colormin: 7.0.2(postcss@8.5.3) - postcss-convert-values: 7.0.4(postcss@8.5.3) - postcss-discard-comments: 7.0.3(postcss@8.5.3) - postcss-discard-duplicates: 7.0.1(postcss@8.5.3) - postcss-discard-empty: 7.0.0(postcss@8.5.3) - postcss-discard-overridden: 7.0.0(postcss@8.5.3) - postcss-merge-longhand: 7.0.4(postcss@8.5.3) - postcss-merge-rules: 7.0.4(postcss@8.5.3) - postcss-minify-font-values: 7.0.0(postcss@8.5.3) - postcss-minify-gradients: 7.0.0(postcss@8.5.3) - postcss-minify-params: 7.0.2(postcss@8.5.3) - postcss-minify-selectors: 7.0.4(postcss@8.5.3) - postcss-normalize-charset: 7.0.0(postcss@8.5.3) - postcss-normalize-display-values: 7.0.0(postcss@8.5.3) - postcss-normalize-positions: 7.0.0(postcss@8.5.3) - postcss-normalize-repeat-style: 7.0.0(postcss@8.5.3) - postcss-normalize-string: 7.0.0(postcss@8.5.3) - postcss-normalize-timing-functions: 7.0.0(postcss@8.5.3) - postcss-normalize-unicode: 7.0.2(postcss@8.5.3) - postcss-normalize-url: 7.0.0(postcss@8.5.3) - postcss-normalize-whitespace: 7.0.0(postcss@8.5.3) - postcss-ordered-values: 7.0.1(postcss@8.5.3) - postcss-reduce-initial: 7.0.2(postcss@8.5.3) - postcss-reduce-transforms: 7.0.0(postcss@8.5.3) - postcss-svgo: 7.0.1(postcss@8.5.3) - postcss-unique-selectors: 7.0.3(postcss@8.5.3) + browserslist: 4.28.0 + css-declaration-sorter: 7.3.0(postcss@8.5.6) + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 10.1.1(postcss@8.5.6) + postcss-colormin: 7.0.5(postcss@8.5.6) + postcss-convert-values: 7.0.8(postcss@8.5.6) + postcss-discard-comments: 7.0.5(postcss@8.5.6) + postcss-discard-duplicates: 7.0.2(postcss@8.5.6) + postcss-discard-empty: 7.0.1(postcss@8.5.6) + postcss-discard-overridden: 7.0.1(postcss@8.5.6) + postcss-merge-longhand: 7.0.5(postcss@8.5.6) + postcss-merge-rules: 7.0.7(postcss@8.5.6) + postcss-minify-font-values: 7.0.1(postcss@8.5.6) + postcss-minify-gradients: 7.0.1(postcss@8.5.6) + postcss-minify-params: 7.0.5(postcss@8.5.6) + postcss-minify-selectors: 7.0.5(postcss@8.5.6) + postcss-normalize-charset: 7.0.1(postcss@8.5.6) + postcss-normalize-display-values: 7.0.1(postcss@8.5.6) + postcss-normalize-positions: 7.0.1(postcss@8.5.6) + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.6) + postcss-normalize-string: 7.0.1(postcss@8.5.6) + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.6) + postcss-normalize-unicode: 7.0.5(postcss@8.5.6) + postcss-normalize-url: 7.0.1(postcss@8.5.6) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.6) + postcss-ordered-values: 7.0.2(postcss@8.5.6) + postcss-reduce-initial: 7.0.5(postcss@8.5.6) + postcss-reduce-transforms: 7.0.1(postcss@8.5.6) + postcss-svgo: 7.1.0(postcss@8.5.6) + postcss-unique-selectors: 7.0.4(postcss@8.5.6) - cssnano-utils@5.0.0(postcss@8.5.3): + cssnano-utils@5.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - cssnano@7.0.6(postcss@8.5.3): + cssnano@7.1.2(postcss@8.5.6): dependencies: - cssnano-preset-default: 7.0.6(postcss@8.5.3) + cssnano-preset-default: 7.0.10(postcss@8.5.6) lilconfig: 3.1.3 - postcss: 8.5.3 + postcss: 8.5.6 csso@5.0.5: dependencies: css-tree: 2.2.1 - cssstyle@4.2.1: - dependencies: - '@asamuzakjp/css-color': 2.8.3 - rrweb-cssom: 0.8.0 + cssom@0.3.8: {} - csstype@3.1.3: {} + cssom@0.5.0: {} - cypress@14.1.0: + cssstyle@2.3.0: dependencies: - '@cypress/request': 3.0.7 + cssom: 0.3.8 + + cssstyle@5.3.6: + dependencies: + '@asamuzakjp/css-color': 4.1.1 + '@csstools/css-syntax-patches-for-csstree': 1.0.22 + css-tree: 3.1.0 + lru-cache: 11.2.4 + optional: true + + csstype@3.2.3: {} + + cypress@15.8.1: + dependencies: + '@cypress/request': 3.0.9 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.3 + '@types/sizzle': 2.3.10 + '@types/tmp': 0.2.6 arch: 2.2.0 blob-util: 2.0.2 bluebird: 3.7.2 buffer: 5.7.1 - cachedir: 2.3.0 + cachedir: 2.4.0 chalk: 4.1.2 - check-more-types: 2.24.0 - ci-info: 4.1.0 + ci-info: 4.3.1 cli-cursor: 3.1.0 - cli-table3: 0.6.3 + cli-table3: 0.6.1 commander: 6.2.1 common-tags: 1.8.2 - dayjs: 1.11.10 - debug: 4.4.0(supports-color@8.1.1) - enquirer: 2.3.6 + dayjs: 1.11.19 + debug: 4.4.3(supports-color@8.1.1) + enquirer: 2.4.1 eventemitter2: 6.4.7 execa: 4.1.0 executable: 4.1.1 extract-zip: 2.0.1(supports-color@8.1.1) figures: 3.2.0 fs-extra: 9.1.0 - getos: 3.2.1 + hasha: 5.2.2 is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.3.6) + listr2: 3.14.0(enquirer@2.4.1) lodash: 4.17.21 log-symbols: 4.1.0 minimist: 1.2.8 @@ -17277,55 +17724,9 @@ snapshots: process: 0.11.10 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.6.3 supports-color: 8.1.1 - tmp: 0.2.3 - tree-kill: 1.2.2 - untildify: 4.0.0 - yauzl: 2.10.0 - - cypress@14.3.0: - dependencies: - '@cypress/request': 3.0.8 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.3 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.3.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - ci-info: 4.1.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.5 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.10 - debug: 4.4.0(supports-color@8.1.1) - enquirer: 2.3.6 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.3.6) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.7.1 - supports-color: 8.1.1 - tmp: 0.2.3 + systeminformation: 5.28.1 + tmp: 0.2.5 tree-kill: 1.2.2 untildify: 4.0.0 yauzl: 2.10.0 @@ -17334,40 +17735,41 @@ snapshots: dependencies: assert-plus: 1.0.0 - data-uri-to-buffer@4.0.0: {} + data-uri-to-buffer@4.0.1: {} - data-urls@5.0.0: + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + + data-urls@6.0.0: dependencies: whatwg-mimetype: 4.0.0 - whatwg-url: 14.1.0 + whatwg-url: 15.1.0 + optional: true - data-view-buffer@1.0.1: + data-view-buffer@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-length@1.0.1: + data-view-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-offset@1.0.0: + data-view-byte-offset@1.0.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 es-errors: 1.3.0 - is-data-view: 1.0.1 - - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.23.4 + is-data-view: 1.0.2 date-fns@4.1.0: {} - dayjs@1.11.10: {} - - dayjs@1.11.13: {} + dayjs@1.11.19: {} de-indent@1.0.2: {} @@ -17381,23 +17783,25 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.3.5: - dependencies: - ms: 2.1.2 - - debug@4.3.7(supports-color@5.5.0): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 - debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 optionalDependencies: supports-color: 5.5.0 - debug@4.4.0(supports-color@8.1.1): + debug@4.4.3(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 optionalDependencies: @@ -17410,32 +17814,36 @@ snapshots: decamelize@1.2.0: {} - decimal.js@10.4.3: {} + decimal.js@10.6.0: {} decode-bmp@0.2.1: dependencies: - '@canvas/image-data': 1.0.0 + '@canvas/image-data': 1.1.0 to-data-view: 1.1.0 decode-ico@0.4.1: dependencies: - '@canvas/image-data': 1.0.0 + '@canvas/image-data': 1.1.0 decode-bmp: 0.2.1 to-data-view: 1.1.0 - decode-named-character-reference@1.0.2: + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 + decompress-response@10.0.0: + dependencies: + mimic-response: 4.0.0 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dedent@1.3.0: {} + dedent@1.7.0: {} deep-email-validator@0.1.21: dependencies: - '@types/disposable-email-domains': 1.0.2 + '@types/disposable-email-domains': 1.0.6 axios: 0.24.0 disposable-email-domains: 1.0.62 mailcheck: 1.1.1 @@ -17444,49 +17852,49 @@ snapshots: deep-eql@5.0.2: {} - deep-equal@2.2.0: + deep-equal@2.2.3: dependencies: - call-bind: 1.0.7 + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.2 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 isarray: 2.0.5 - object-is: 1.1.5 + object-is: 1.1.6 object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.15 - - deep-extend@0.6.0: - optional: true + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 deep-is@0.1.4: {} - deepmerge@4.2.2: {} + deepmerge@4.3.1: {} - defaults@3.0.0: {} + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + defaults@2.0.2: {} defer-to-connect@2.0.1: {} define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - define-lazy-prop@2.0.0: {} - - define-properties@1.2.0: - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 + define-lazy-prop@3.0.0: {} define-properties@1.2.1: dependencies: @@ -17510,7 +17918,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.0.3: {} + detect-libc@2.1.2: {} detect-newline@3.1.0: {} @@ -17518,13 +17926,20 @@ snapshots: dependencies: dequal: 2.0.3 + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff-match-patch@1.0.5: {} diff-sequences@29.6.3: {} diff@5.2.0: {} - dijkstrajs@1.0.2: {} + diff@8.0.2: {} + + dijkstrajs@1.0.3: {} dir-glob@3.0.1: dependencies: @@ -17562,6 +17977,10 @@ snapshots: domelementtype@2.3.0: {} + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + domhandler@3.3.0: dependencies: domelementtype: 2.3.0 @@ -17580,19 +17999,13 @@ snapshots: domelementtype: 2.3.0 domhandler: 4.3.1 - domutils@3.0.1: + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - domutils@3.1.0: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - dotenv@16.5.0: {} + dotenv@16.6.1: {} dunder-proto@1.0.1: dependencies: @@ -17618,25 +18031,27 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.7.1 + semver: 7.7.3 ee-first@1.1.1: {} - electron-to-chromium@1.5.83: {} + electron-to-chromium@1.5.255: {} emittery@0.13.1: {} - emoji-regex-xs@1.0.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + empathic@2.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} - encoding-sniffer@0.2.0: + encoding-sniffer@0.2.1: dependencies: iconv-lite: 0.6.3 whatwg-encoding: 3.1.1 @@ -17646,120 +18061,87 @@ snapshots: iconv-lite: 0.6.3 optional: true - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 - enquirer@2.3.6: + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 + strip-ansi: 6.0.1 entities@2.2.0: {} entities@4.5.0: {} + entities@6.0.1: {} + + entities@7.0.0: {} + env-paths@2.2.1: {} err-code@2.0.3: {} - error-ex@1.3.2: + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - es-abstract@1.22.1: + es-abstract@1.24.0: dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.1 - available-typed-arrays: 1.0.5 - call-bind: 1.0.7 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.2 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.10 - is-weakref: 1.0.2 - object-inspect: 1.13.2 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-array-concat: 1.0.0 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.11 - - es-abstract@1.23.3: - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.3 - gopd: 1.0.1 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 + has-proto: 1.2.0 + has-symbols: 1.1.0 hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 is-callable: 1.2.7 - is-data-view: 1.0.1 + is-data-view: 1.0.2 is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.2 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 es-define-property@1.0.1: {} @@ -17767,54 +18149,40 @@ snapshots: es-get-iterator@1.1.3: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - is-arguments: 1.1.1 - is-map: 2.0.2 - is-set: 2.0.2 - is-string: 1.0.7 + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.1.1 isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 + stop-iteration-iterator: 1.1.0 - es-module-lexer@1.6.0: {} - - es-object-atoms@1.0.0: - dependencies: - es-errors: 1.3.0 + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.0.0: - dependencies: - has: 1.0.3 - - es-shim-unscopables@1.0.2: + es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.2 - es-to-primitive@1.2.1: + es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 + is-date-object: 1.1.0 + is-symbol: 1.1.1 - es-toolkit@1.27.0: {} + es-toolkit@1.42.0: {} es6-promise@4.2.8: optional: true @@ -17824,12 +18192,12 @@ snapshots: es6-promise: 4.2.8 optional: true - esbuild-register@3.5.0(esbuild@0.25.2): + esbuild-plugin-swc@1.0.1: dependencies: - debug: 4.4.0(supports-color@5.5.0) - esbuild: 0.25.2 + '@swc/core': 1.15.7 + deepmerge: 4.3.1 transitivePeerDependencies: - - supports-color + - '@swc/helpers' esbuild@0.19.11: optionalDependencies: @@ -17857,63 +18225,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 - esbuild@0.25.0: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.0 - '@esbuild/android-arm': 0.25.0 - '@esbuild/android-arm64': 0.25.0 - '@esbuild/android-x64': 0.25.0 - '@esbuild/darwin-arm64': 0.25.0 - '@esbuild/darwin-x64': 0.25.0 - '@esbuild/freebsd-arm64': 0.25.0 - '@esbuild/freebsd-x64': 0.25.0 - '@esbuild/linux-arm': 0.25.0 - '@esbuild/linux-arm64': 0.25.0 - '@esbuild/linux-ia32': 0.25.0 - '@esbuild/linux-loong64': 0.25.0 - '@esbuild/linux-mips64el': 0.25.0 - '@esbuild/linux-ppc64': 0.25.0 - '@esbuild/linux-riscv64': 0.25.0 - '@esbuild/linux-s390x': 0.25.0 - '@esbuild/linux-x64': 0.25.0 - '@esbuild/netbsd-arm64': 0.25.0 - '@esbuild/netbsd-x64': 0.25.0 - '@esbuild/openbsd-arm64': 0.25.0 - '@esbuild/openbsd-x64': 0.25.0 - '@esbuild/sunos-x64': 0.25.0 - '@esbuild/win32-arm64': 0.25.0 - '@esbuild/win32-ia32': 0.25.0 - '@esbuild/win32-x64': 0.25.0 - - esbuild@0.25.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.2 - '@esbuild/android-arm': 0.25.2 - '@esbuild/android-arm64': 0.25.2 - '@esbuild/android-x64': 0.25.2 - '@esbuild/darwin-arm64': 0.25.2 - '@esbuild/darwin-x64': 0.25.2 - '@esbuild/freebsd-arm64': 0.25.2 - '@esbuild/freebsd-x64': 0.25.2 - '@esbuild/linux-arm': 0.25.2 - '@esbuild/linux-arm64': 0.25.2 - '@esbuild/linux-ia32': 0.25.2 - '@esbuild/linux-loong64': 0.25.2 - '@esbuild/linux-mips64el': 0.25.2 - '@esbuild/linux-ppc64': 0.25.2 - '@esbuild/linux-riscv64': 0.25.2 - '@esbuild/linux-s390x': 0.25.2 - '@esbuild/linux-x64': 0.25.2 - '@esbuild/netbsd-arm64': 0.25.2 - '@esbuild/netbsd-x64': 0.25.2 - '@esbuild/openbsd-arm64': 0.25.2 - '@esbuild/openbsd-x64': 0.25.2 - '@esbuild/sunos-x64': 0.25.2 - '@esbuild/win32-arm64': 0.25.2 - '@esbuild/win32-ia32': 0.25.2 - '@esbuild/win32-x64': 0.25.2 - - escalade@3.1.1: {} + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escalade@3.2.0: {} @@ -17931,6 +18270,14 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + eslint-formatter-pretty@4.1.0: dependencies: '@types/eslint': 7.29.0 @@ -17945,180 +18292,143 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7(supports-color@8.1.1) - is-core-module: 2.15.1 - resolve: 1.22.8 + is-core-module: 2.16.1 + resolve: 1.22.11 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@9.22.0)(typescript@5.3.3) - eslint: 9.22.0 + '@typescript-eslint/parser': 6.18.1(eslint@9.39.2)(typescript@5.3.3) + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2) - eslint: 9.22.0 + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0): - dependencies: - debug: 3.2.7(supports-color@8.1.1) - optionalDependencies: - '@typescript-eslint/parser': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - eslint: 9.22.0 - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint@9.22.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 9.22.0 + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.18.1(eslint@9.22.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.18.1(eslint@9.39.2)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) hasown: 2.0.2 - is-core-module: 2.15.1 + is-core-module: 2.16.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 object.groupby: 1.0.3 - object.values: 1.2.0 + object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: 1.0.8 + string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@9.22.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.18.1(eslint@9.39.2)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 9.22.0 + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) hasown: 2.0.2 - is-core-module: 2.15.1 + is-core-module: 2.16.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 object.groupby: 1.0.3 - object.values: 1.2.0 + object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: 1.0.8 + string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2) + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint@9.22.0): + eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.5.0(eslint@9.39.2))(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)): dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@8.1.1) - doctrine: 2.1.0 - eslint: 9.22.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.22.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0) - hasown: 2.0.2 - is-core-module: 2.15.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - string.prototype.trimend: 1.0.8 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.29.1(eslint@9.22.0)(typescript@5.8.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-vue@10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.3(eslint@9.22.0)): - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - eslint: 9.22.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + eslint: 9.39.2 natural-compare: 1.4.0 nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.6.3 - vue-eslint-parser: 10.1.3(eslint@9.22.0) + postcss-selector-parser: 7.1.0 + semver: 7.7.3 + vue-eslint-parser: 10.2.0(eslint@9.39.2) xml-name-validator: 4.0.0 + optionalDependencies: + '@stylistic/eslint-plugin': 5.5.0(eslint@9.39.2) + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) eslint-rule-docs@1.1.235: {} - eslint-scope@8.3.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.22.0: + eslint@9.39.2: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/config-helpers': 0.1.0 - '@eslint/core': 0.12.0 - '@eslint/eslintrc': 3.3.0 - '@eslint/js': 9.22.0 - '@eslint/plugin-kit': 0.2.7 - '@humanfs/node': 0.16.6 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.7 - '@types/json-schema': 7.0.15 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.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.1 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 @@ -18129,11 +18439,13 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.3.0: + esm-resolve@1.0.11: {} + + espree@10.4.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -18151,7 +18463,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 esutils@2.0.3: {} @@ -18175,6 +18487,12 @@ snapshots: eventemitter3@5.0.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.1 + transitivePeerDependencies: + - bare-abort-controller + events@3.3.0: {} execa@4.1.0: @@ -18213,43 +18531,32 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 3.0.0 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - execa@9.5.2: + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.6 figures: 6.1.0 get-stream: 9.0.1 - human-signals: 8.0.0 + human-signals: 8.0.1 is-plain-obj: 4.1.0 is-stream: 4.0.1 npm-run-path: 6.0.0 - pretty-ms: 9.2.0 + pretty-ms: 9.3.0 signal-exit: 4.1.0 strip-final-newline: 4.0.0 - yoctocolors: 2.1.1 + yoctocolors: 2.1.2 executable@4.1.1: dependencies: pify: 2.3.0 + exifreader@4.33.1: + optionalDependencies: + '@xmldom/xmldom': 0.9.8 + exit@0.1.2: {} - expand-template@2.0.3: - optional: true - - expect-type@1.2.1: {} + expect-type@1.2.2: {} expect@29.7.0: dependencies: @@ -18259,43 +18566,7 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - exponential-backoff@3.1.1: {} - - express@4.21.1: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.10 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color + exponential-backoff@3.1.3: {} express@4.21.2: dependencies: @@ -18333,9 +18604,42 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.1 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + ext-list@2.2.2: dependencies: - mime-db: 1.52.0 + mime-db: 1.54.0 ext-name@5.0.0: dependencies: @@ -18346,23 +18650,23 @@ snapshots: extract-zip@2.0.1(supports-color@8.1.1): dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: - '@types/yauzl': 2.10.0 + '@types/yauzl': 2.10.3 transitivePeerDependencies: - supports-color extsprintf@1.3.0: {} - fast-content-type-parse@2.0.0: {} + fast-content-type-parse@3.0.0: {} fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} - fast-fifo@1.3.0: {} + fast-fifo@1.3.2: {} fast-glob@3.3.3: dependencies: @@ -18374,14 +18678,13 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-json-stringify@6.0.0: + fast-json-stringify@6.1.1: dependencies: - '@fastify/merge-json-schemas': 0.1.1 + '@fastify/merge-json-schemas': 0.2.1 ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) - fast-deep-equal: 3.1.3 - fast-uri: 2.4.0 - json-schema-ref-resolver: 1.0.1 + fast-uri: 3.1.0 + json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 fast-levenshtein@2.0.6: {} @@ -18390,53 +18693,43 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 - fast-redact@3.1.2: {} - fast-safe-stringify@2.1.1: {} - fast-uri@2.4.0: {} + fast-uri@3.1.0: {} - fast-uri@3.0.1: {} - - fast-xml-parser@4.4.1: + fast-xml-parser@5.2.5: dependencies: - strnum: 1.0.5 + strnum: 2.1.1 - fast-xml-parser@4.5.0: - dependencies: - strnum: 1.0.5 - - fastify-plugin@4.5.1: {} - - fastify-plugin@5.0.0: {} + fastify-plugin@5.1.0: {} fastify-raw-body@5.0.0: dependencies: - fastify-plugin: 5.0.0 - raw-body: 3.0.0 + fastify-plugin: 5.1.0 + raw-body: 3.0.1 secure-json-parse: 2.7.0 - fastify@5.2.2: + fastify@5.6.2: dependencies: - '@fastify/ajv-compiler': 4.0.0 - '@fastify/error': 4.0.0 - '@fastify/fast-json-stringify-compiler': 5.0.0 - '@fastify/proxy-addr': 5.0.0 + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.1.0 abstract-logging: 2.0.1 - avvio: 9.0.0 - fast-json-stringify: 6.0.0 - find-my-way: 9.0.1 - light-my-request: 6.0.0 - pino: 9.2.0 - process-warning: 4.0.0 + avvio: 9.1.0 + fast-json-stringify: 6.1.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 10.1.0 + process-warning: 5.0.0 rfdc: 1.4.1 - secure-json-parse: 3.0.2 - semver: 7.7.1 + secure-json-parse: 4.1.0 + semver: 7.7.3 toad-cache: 3.7.0 - fastq@1.17.1: + fastq@1.19.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 fb-watchman@2.0.2: dependencies: @@ -18446,18 +18739,20 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.4.3(picomatch@4.0.2): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 - feed@4.2.2: + feed@5.1.0: dependencies: xml-js: 1.6.11 fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 - web-streams-polyfill: 3.2.1 + web-streams-polyfill: 3.3.3 + + fflate@0.8.2: {} figures@3.2.0: dependencies: @@ -18471,12 +18766,32 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-type@19.6.0: + file-type@20.5.0: dependencies: - get-stream: 9.0.1 - strtok3: 9.0.1 - token-types: 6.0.0 - uint8array-extras: 1.4.0 + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + file-type@21.1.1: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + file-type@21.2.0: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color filename-reserved-regex@3.0.0: {} @@ -18500,13 +18815,22 @@ snapshots: transitivePeerDependencies: - supports-color - find-my-way@9.0.1: + finalhandler@2.1.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-my-way@9.3.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 - safe-regex2: 4.0.0 - - find-package-json@1.2.0: {} + safe-regex2: 5.0.0 find-up@4.1.0: dependencies: @@ -18522,38 +18846,35 @@ snapshots: dependencies: semver-regex: 4.0.5 - fkill@9.0.0: + fkill@10.0.1: dependencies: - aggregate-error: 5.0.0 - execa: 8.0.1 - pid-port: 1.0.2 + execa: 9.6.1 + pid-port: 2.0.0 process-exists: 5.0.0 - ps-list: 8.1.1 + ps-list: 9.0.0 taskkill: 5.0.0 flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.1: {} + flatted@3.3.3: {} fluent-ffmpeg@2.1.3: dependencies: async: 0.2.10 which: 1.3.1 - follow-redirects@1.15.2: {} - - follow-redirects@1.15.9(debug@4.4.0): + follow-redirects@1.15.11(debug@4.4.3): optionalDependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) - for-each@0.3.3: + for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@3.1.1: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 @@ -18562,35 +18883,41 @@ snapshots: form-data-encoder@2.1.4: {} - form-data-encoder@4.0.2: {} + form-data-encoder@4.1.0: {} - form-data@4.0.2: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded-parse@2.1.2: {} forwarded@0.2.0: {} fresh@0.5.2: {} + fresh@2.0.0: {} + from@0.1.7: {} - fs-constants@1.0.0: - optional: true - - fs-extra@11.3.0: + fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 + jsonfile: 6.2.0 + universalify: 2.0.1 fs-extra@8.1.0: dependencies: @@ -18602,12 +18929,13 @@ snapshots: dependencies: at-least-node: 1.0.0 graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 + jsonfile: 6.2.0 + universalify: 2.0.1 fs-minipass@2.1.0: dependencies: minipass: 3.3.6 + optional: true fs-minipass@3.0.3: dependencies: @@ -18620,25 +18948,20 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.5: + function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 - functions-have-names: 1.2.3 - - function.prototype.name@1.1.6: - dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.3 functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 functions-have-names@1.2.3: {} gauge@3.0.2: dependencies: - aproba: 2.0.0 + aproba: 2.1.0 color-support: 1.1.3 console-control-strings: 1.1.0 has-unicode: 2.0.1 @@ -18649,30 +18972,13 @@ snapshots: wide-align: 1.1.5 optional: true + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} - get-intrinsic@1.2.4: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - - get-intrinsic@1.2.7: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 + get-east-asian-width@1.4.0: {} get-intrinsic@1.3.0: dependencies: @@ -18692,51 +18998,33 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-stream@5.2.0: dependencies: - pump: 3.0.0 + pump: 3.0.3 get-stream@6.0.1: {} - get-stream@8.0.1: {} - get-stream@9.0.1: dependencies: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-symbol-description@1.0.0: + get-symbol-description@1.1.0: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - - get-symbol-description@1.0.2: - dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 - get-tsconfig@4.10.0: + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 - get-tsconfig@4.9.0: - dependencies: - resolve-pkg-maps: 1.0.0 - - getos@3.2.1: - dependencies: - async: 3.2.4 - getpass@0.1.7: dependencies: assert-plus: 1.0.0 - github-from-package@0.0.0: - optional: true - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -18747,29 +19035,35 @@ snapshots: glob@10.3.10: dependencies: - foreground-child: 3.1.1 + foreground-child: 3.3.1 jackspeak: 2.3.6 minimatch: 9.0.4 minipass: 7.1.2 path-scurry: 1.11.1 - glob@10.4.5: + glob@10.5.0: dependencies: - foreground-child: 3.1.1 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.4 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.0.1: + glob@11.1.0: dependencies: - foreground-child: 3.1.1 - jackspeak: 4.0.1 - minimatch: 10.0.1 + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 minipass: 7.1.2 - package-json-from-dist: 1.0.0 - path-scurry: 2.0.0 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + glob@13.0.0: + dependencies: + minimatch: 10.1.1 + minipass: 7.1.2 + path-scurry: 2.0.1 glob@7.2.3: dependencies: @@ -18780,44 +19074,31 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.2 - once: 1.4.0 - global-dirs@3.0.1: dependencies: ini: 2.0.0 - globals@11.12.0: {} - globals@14.0.0: {} - globals@16.0.0: {} + globals@16.5.0: {} - globalthis@1.0.3: + globalthis@1.0.4: dependencies: - define-properties: 1.2.0 + define-properties: 1.2.1 + gopd: 1.2.0 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.3 - ignore: 5.3.1 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 - google-protobuf@3.21.2: + google-protobuf@3.21.4: optional: true - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 - gopd@1.2.0: {} got@13.0.0: @@ -18834,82 +19115,68 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 - got@14.4.7: + got@14.6.5: dependencies: - '@sindresorhus/is': 7.0.1 - '@szmarczak/http-timer': 5.0.1 + '@sindresorhus/is': 7.1.1 + byte-counter: 0.1.0 cacheable-lookup: 7.0.0 - cacheable-request: 12.0.1 - decompress-response: 6.0.0 - form-data-encoder: 4.0.2 + cacheable-request: 13.0.15 + decompress-response: 10.0.0 + form-data-encoder: 4.1.0 http2-wrapper: 2.2.1 + keyv: 5.5.4 lowercase-keys: 3.0.0 p-cancelable: 4.0.1 - responselike: 3.0.0 - type-fest: 4.26.1 + responselike: 4.0.2 + type-fest: 4.41.0 graceful-fs@4.2.11: {} graphemer@1.4.0: {} - graphql@16.8.1: {} + graphql@16.12.0: {} hammerjs@2.0.8: {} - happy-dom@16.8.1: + happy-dom@20.0.11: dependencies: - webidl-conversions: 7.0.0 - whatwg-mimetype: 3.0.0 - - happy-dom@17.4.4: - dependencies: - webidl-conversions: 7.0.0 + '@types/node': 20.19.25 + '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 hard-rejection@2.1.0: {} - has-bigints@1.0.2: {} + harfbuzzjs@0.4.14: {} + + has-bigints@1.1.0: {} has-flag@3.0.0: {} has-flag@4.0.0: {} - has-property-descriptors@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 - has-proto@1.0.1: {} - - has-proto@1.0.3: {} - - has-symbols@1.0.3: {} + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 has-symbols@1.1.0: {} - has-tostringtag@1.0.0: - dependencies: - has-symbols: 1.0.3 - has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 has-unicode@2.0.1: optional: true - has@1.0.3: - dependencies: - function-bind: 1.1.2 - hash-sum@2.0.0: {} - hasown@2.0.0: + hasha@5.2.2: dependencies: - function-bind: 1.1.2 + is-stream: 2.0.1 + type-fest: 0.8.1 hasown@2.0.2: dependencies: @@ -18918,13 +19185,13 @@ snapshots: hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 ccount: 2.0.1 comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.0 - property-information: 7.0.0 + property-information: 7.1.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 @@ -18935,11 +19202,9 @@ snapshots: he@1.2.0: {} - headers-polyfill@4.0.2: {} + headers-polyfill@4.0.3: {} - highlight.js@10.7.3: {} - - highlight.js@11.10.0: {} + highlight.js@11.11.1: {} hosted-git-info@2.8.9: {} @@ -18949,17 +19214,27 @@ snapshots: hpagent@1.2.0: {} + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 + optional: true - html-entities@2.5.2: {} + html-entities@2.6.0: {} html-escaper@2.0.2: {} html-void-elements@3.0.0: {} - htmlescape@1.1.1: {} + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 htmlparser2@5.0.1: dependencies: @@ -18968,21 +19243,21 @@ snapshots: domutils: 2.8.0 entities: 2.2.0 - htmlparser2@8.0.1: + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.0.1 + domutils: 3.2.2 entities: 4.5.0 htmlparser2@9.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 entities: 4.5.0 - http-cache-semantics@4.1.1: {} + http-cache-semantics@4.2.0: {} http-errors@2.0.0: dependencies: @@ -18994,10 +19269,18 @@ snapshots: http-link-header@1.1.3: {} + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.0 - debug: 4.4.0(supports-color@5.5.0) + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -19025,22 +19308,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - optional: true - - https-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: - agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -19050,9 +19325,7 @@ snapshots: human-signals@3.0.1: {} - human-signals@5.0.0: {} - - human-signals@8.0.0: {} + human-signals@8.0.1: {} iconv-lite@0.4.24: dependencies: @@ -19062,35 +19335,41 @@ snapshots: dependencies: safer-buffer: 2.1.2 - idb-keyval@6.2.1: {} + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + idb-keyval@6.2.2: {} ieee754@1.2.1: {} ignore-by-default@1.0.1: {} - ignore-walk@7.0.0: + ignore-walk@8.0.0: dependencies: - minimatch: 9.0.4 + minimatch: 10.1.1 - ignore@5.3.1: {} + ignore@5.3.2: {} - immutable@5.0.3: {} + ignore@7.0.5: {} - import-fresh@3.3.0: + immutable@5.1.4: {} + + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.11.2: + import-in-the-middle@2.0.0: dependencies: - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) - cjs-module-lexer: 1.2.2 - module-details-from-path: 1.0.3 + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 import-lazy@4.0.0: {} - import-local@3.1.0: + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 @@ -19099,7 +19378,7 @@ snapshots: indent-string@4.0.0: {} - indent-string@5.0.0: {} + index-to-position@1.2.0: {} inflight@1.0.6: dependencies: @@ -19118,27 +19397,21 @@ snapshots: dependencies: kind-of: 6.0.3 - install-artifact-from-github@1.3.5: {} + install-artifact-from-github@1.4.0: {} - internal-slot@1.0.5: - dependencies: - get-intrinsic: 1.2.4 - has: 1.0.3 - side-channel: 1.0.6 - - internal-slot@1.0.7: + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 intersection-observer@0.12.2: {} - ioredis@5.6.0: + ioredis@5.8.2: dependencies: - '@ioredis/commands': 1.2.0 + '@ioredis/commands': 1.4.0 cluster-key-slot: 1.1.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -19148,6 +19421,10 @@ snapshots: transitivePeerDependencies: - supports-color + ios-haptics@0.1.4: {} + + ip-address@10.0.1: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -19159,58 +19436,62 @@ snapshots: ip-regex@5.0.0: {} - ip@2.0.1: {} - ipaddr.js@1.9.1: {} - ipaddr.js@2.2.0: {} + ipaddr.js@2.3.0: {} irregular-plurals@3.5.0: {} - is-arguments@1.1.1: + is-arguments@1.2.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-array-buffer@3.0.2: + is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-typed-array: 1.1.13 - - is-array-buffer@3.0.4: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-arrayish@0.2.1: {} - is-arrayish@0.3.2: {} + is-arrayish@0.3.4: {} - is-bigint@1.0.4: + is-async-function@2.1.1: dependencies: - has-bigints: 1.0.2 + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 - is-boolean-object@1.1.2: + is-bigint@1.1.0: dependencies: - call-bind: 1.0.7 + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-callable@1.2.7: {} - is-core-module@2.15.1: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: + is-data-view@1.0.2: dependencies: - is-typed-array: 1.1.13 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 - is-date-object@1.0.5: + is-date-object@1.1.0: dependencies: + call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-docker@2.2.1: {} + is-docker@3.0.0: {} is-expression@4.0.0: dependencies: @@ -19221,35 +19502,44 @@ snapshots: is-file-animated@1.0.2: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} is-generator-fn@2.1.0: {} - is-generator-function@1.0.10: + is-generator-function@1.1.2: dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 is-path-inside: 3.0.3 - is-lambda@1.0.1: {} - - is-map@2.0.2: {} - - is-negative-zero@2.0.2: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} is-node-process@1.2.0: {} - is-number-object@1.0.7: + is-number-object@1.1.1: dependencies: + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -19266,20 +19556,20 @@ snapshots: is-promise@2.2.2: {} - is-regex@1.1.4: + is-promise@4.0.0: {} + + is-regex@1.2.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 + gopd: 1.2.0 has-tostringtag: 1.0.2 + hasown: 2.0.2 - is-set@2.0.2: {} + is-set@2.0.3: {} - is-shared-array-buffer@1.0.2: + is-shared-array-buffer@1.0.4: dependencies: - call-bind: 1.0.7 - - is-shared-array-buffer@1.0.3: - dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 is-stream@2.0.1: {} @@ -19287,29 +19577,24 @@ snapshots: is-stream@4.0.1: {} - is-string@1.0.7: + is-string@1.1.1: dependencies: - has-tostringtag: 1.0.0 - - is-svg@5.1.0: - dependencies: - fast-xml-parser: 4.5.0 - - is-symbol@1.0.4: - dependencies: - has-symbols: 1.0.3 - - is-typed-array@1.1.10: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.2.0 + call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-typed-array@1.1.13: + is-svg@6.1.0: dependencies: - which-typed-array: 1.1.15 + '@file-type/xml': 0.4.4 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 is-typedarray@1.0.0: {} @@ -19317,20 +19602,20 @@ snapshots: is-unicode-supported@2.1.0: {} - is-weakmap@2.0.1: {} + is-weakmap@2.0.2: {} - is-weakref@1.0.2: + is-weakref@1.1.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 - is-weakset@2.0.2: + is-weakset@2.0.4: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 - is-wsl@2.2.0: + is-wsl@3.1.0: dependencies: - is-docker: 2.2.1 + is-inside-container: 1.0.0 isarray@1.0.0: {} @@ -19346,21 +19631,21 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.25.6 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color - istanbul-lib-instrument@6.0.0: + istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.25.6 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.1 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -19372,7 +19657,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -19380,13 +19665,13 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0(supports-color@5.5.0) + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3(supports-color@10.2.2) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.7: + istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 @@ -19405,11 +19690,9 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.0.1: + jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 jest-changed-files@29.7.0: dependencies: @@ -19423,10 +19706,10 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 chalk: 4.1.2 co: 4.6.0 - dedent: 1.3.0 + dedent: 1.7.0 is-generator-fn: 2.1.0 jest-each: 29.7.0 jest-matcher-utils: 29.7.0 @@ -19436,7 +19719,7 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 - pure-rand: 6.0.0 + pure-rand: 6.1.0 slash: 3.0.0 stack-utils: 2.0.6 transitivePeerDependencies: @@ -19451,7 +19734,7 @@ snapshots: chalk: 4.1.2 create-jest: 29.7.0(@types/node@20.11.17) exit: 0.1.2 - import-local: 3.1.0 + import-local: 3.2.0 jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 jest-validate: 29.7.0 @@ -19462,35 +19745,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.13.15): + jest-cli@29.7.0(@types/node@24.10.4): 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.13.15) + create-jest: 29.7.0(@types/node@24.10.4) exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.13.15) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-cli@29.7.0(@types/node@22.14.0): - 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.14.0) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.14.0) + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@24.10.4) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -19502,13 +19766,13 @@ snapshots: jest-config@29.7.0(@types/node@20.11.17): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7) + babel-jest: 29.7.0(@babel/core@7.28.5) chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 jest-circus: 29.7.0 @@ -19530,15 +19794,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.13.15): + jest-config@29.7.0(@types/node@24.10.4): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7) + babel-jest: 29.7.0(@babel/core@7.28.5) chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 jest-circus: 29.7.0 @@ -19555,37 +19819,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.13.15 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.14.0): - dependencies: - '@babel/core': 7.24.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7) - chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -19609,29 +19843,37 @@ snapshots: jest-util: 29.7.0 pretty-format: 29.7.0 + jest-environment-jsdom@29.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 24.10.4 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 jest-mock: 29.7.0 jest-util: 29.7.0 - jest-fetch-mock@3.0.3(encoding@0.1.13): - dependencies: - cross-fetch: 3.1.6(encoding@0.1.13) - promise-polyfill: 8.3.0 - transitivePeerDependencies: - - encoding - jest-get-type@29.6.3: {} jest-haste-map@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.6 - '@types/node': 22.14.0 + '@types/graceful-fs': 4.1.9 + '@types/node': 24.10.4 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -19657,9 +19899,9 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.27.1 '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.1 + '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.8 @@ -19670,7 +19912,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -19679,6 +19921,8 @@ snapshots: jest-regex-util@29.6.3: {} + jest-regex-util@30.0.1: {} + jest-resolve-dependencies@29.7.0: dependencies: jest-regex-util: 29.6.3 @@ -19694,8 +19938,8 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-util: 29.7.0 jest-validate: 29.7.0 - resolve: 1.22.8 - resolve.exports: 2.0.0 + resolve: 1.22.11 + resolve.exports: 2.0.3 slash: 3.0.0 jest-runner@29.7.0: @@ -19705,7 +19949,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -19733,10 +19977,10 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 chalk: 4.1.2 - cjs-module-lexer: 1.2.2 - collect-v8-coverage: 1.0.1 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 glob: 7.2.3 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 @@ -19753,15 +19997,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.23.5 - '@babel/generator': 7.23.5 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5) - '@babel/types': 7.25.6 + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/types': 7.28.5 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -19772,16 +20016,16 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 chalk: 4.1.2 - ci-info: 3.7.1 + ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 @@ -19798,21 +20042,16 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 24.10.4 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 - jest-websocket-mock@2.5.0: - dependencies: - jest-diff: 29.7.0 - mock-socket: 9.3.1 - jest-worker@29.7.0: dependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -19821,7 +20060,7 @@ snapshots: dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 - import-local: 3.1.0 + import-local: 3.2.0 jest-cli: 29.7.0(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' @@ -19829,24 +20068,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@22.13.15): + jest@29.7.0(@types/node@24.10.4): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.13.15) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@22.14.0): - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.14.0) + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@24.10.4) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -19855,14 +20082,6 @@ snapshots: jju@1.4.0: {} - joi@17.11.0: - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.4 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - joi@17.13.3: dependencies: '@hapi/hoek': 9.3.0 @@ -19871,23 +20090,40 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - js-beautify@1.14.9: + joi@18.0.1: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.4 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.0.0 + + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 editorconfig: 1.0.4 - glob: 8.1.0 - nopt: 6.0.0 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + + js-levenshtein@1.1.6: {} js-stringify@1.0.2: {} js-tokens@4.0.0: {} - js-yaml@3.14.1: + js-tokens@9.0.1: {} + + js-yaml@3.14.2: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -19897,47 +20133,76 @@ snapshots: jschardet@3.1.4: {} - jsdoc-type-pratt-parser@4.1.0: {} - - jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5): + jsdom@20.0.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: - cssstyle: 4.2.1 - data-urls: 5.0.0 - decimal.js: 10.4.3 - form-data: 4.0.2 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + abab: 2.0.6 + acorn: 8.15.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.6.0 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.5 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.16 - parse5: 7.2.1 - rrweb-cssom: 0.8.0 + nwsapi: 2.2.23 + parse5: 7.3.0 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 5.0.0 - w3c-xmlserializer: 5.0.0 + tough-cookie: 4.1.4 + w3c-xmlserializer: 4.0.0 webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.1.0 - ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) - xml-name-validator: 5.0.0 - optionalDependencies: - canvas: 3.1.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) + xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - jsesc@2.5.2: {} + jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + dependencies: + '@acemir/cssom': 0.9.30 + '@asamuzakjp/dom-selector': 6.7.6 + cssstyle: 5.3.6 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + optional: true + + jsesc@3.1.0: {} json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} - json-schema-ref-resolver@1.0.1: + json-schema-ref-resolver@3.0.0: dependencies: - fast-deep-equal: 3.1.3 + dequal: 2.0.3 json-schema-traverse@0.4.1: {} @@ -19955,7 +20220,7 @@ snapshots: json5@2.2.3: {} - jsonc-parser@3.2.0: {} + jsonc-parser@3.3.1: {} jsonfile@4.0.0: optionalDependencies: @@ -19967,20 +20232,18 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.1.0: + jsonfile@6.2.0: dependencies: - universalify: 2.0.0 + universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - jsonld@8.3.3(web-streams-polyfill@4.0.0): + jsonld@9.0.0: dependencies: - '@digitalbazaar/http-client': 3.4.1(web-streams-polyfill@4.0.0) - canonicalize: 1.0.8 + '@digitalbazaar/http-client': 4.2.0 + canonicalize: 2.1.0 lru-cache: 6.0.0 - rdf-canonize: 3.4.0 - transitivePeerDependencies: - - web-streams-polyfill + rdf-canonize: 5.0.0 jsonpointer@5.0.1: {} @@ -19998,25 +20261,23 @@ snapshots: json-schema: 0.4.0 verror: 1.10.0 - jsrsasign@11.1.0: {} - jstransformer@1.0.0: dependencies: is-promise: 2.2.2 promise: 7.3.1 - juice@11.0.1: + juice@11.0.3: dependencies: cheerio: 1.0.0 commander: 12.1.0 - entities: 4.5.0 + entities: 7.0.0 mensch: 0.3.4 slick: 1.12.2 web-resource-inliner: 7.0.0 just-extend@6.2.0: {} - jwa@2.0.0: + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 @@ -20024,32 +20285,28 @@ snapshots: jws@4.0.0: dependencies: - jwa: 2.0.0 + jwa: 2.0.1 safe-buffer: 5.2.1 keyv@4.5.4: dependencies: json-buffer: 3.0.1 + keyv@5.5.4: + dependencies: + '@keyv/serialize': 1.1.1 + kind-of@6.0.3: {} kleur@3.0.3: {} - ky-universal@0.11.0(ky@0.33.3)(web-streams-polyfill@4.0.0): - dependencies: - abort-controller: 3.0.0 - ky: 0.33.3 - node-fetch: 3.3.2 - optionalDependencies: - web-streams-polyfill: 4.0.0 - - ky@0.33.3: {} + ky@1.14.0: {} lazy-ass@1.6.0: {} lazystream@1.0.1: dependencies: - readable-stream: 2.3.7 + readable-stream: 2.3.8 leven@3.1.0: {} @@ -20058,20 +20315,20 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - light-my-request@6.0.0: + light-my-request@6.6.0: dependencies: - cookie: 0.6.0 - process-warning: 4.0.0 - set-cookie-parser: 2.6.0 + cookie: 1.0.2 + process-warning: 4.0.1 + set-cookie-parser: 2.7.1 lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} - listr2@3.14.0(enquirer@2.3.6): + listr2@3.14.0(enquirer@2.4.1): dependencies: cli-truncate: 2.1.0 - colorette: 2.0.19 + colorette: 2.0.20 log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.4.1 @@ -20079,7 +20336,9 @@ snapshots: through: 2.3.8 wrap-ansi: 7.0.0 optionalDependencies: - enquirer: 2.3.6 + enquirer: 2.4.1 + + load-esm@1.0.3: {} locate-path@5.0.0: dependencies: @@ -20091,8 +20350,6 @@ snapshots: lodash.defaults@4.2.0: {} - lodash.get@4.4.2: {} - lodash.isarguments@3.1.0: {} lodash.memoize@4.1.2: {} @@ -20121,13 +20378,13 @@ snapshots: longest-streak@3.1.0: {} - loupe@3.1.3: {} + loupe@3.2.1: {} lowercase-keys@3.0.0: {} lru-cache@10.4.3: {} - lru-cache@11.0.0: {} + lru-cache@11.2.4: {} lru-cache@5.1.1: dependencies: @@ -20137,24 +20394,20 @@ snapshots: dependencies: yallist: 4.0.0 - lru-cache@8.0.4: {} + lru-cache@8.0.5: {} - luxon@3.3.0: {} + luxon@3.7.2: {} lz-string@1.5.0: {} - magic-string@0.27.0: + magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@0.30.17: + magicast@0.5.1: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - - magicast@0.3.5: - dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 source-map-js: 1.2.1 mailcheck@1.1.1: {} @@ -20166,23 +20419,23 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.3 make-error@1.3.6: {} - make-fetch-happen@13.0.0: + make-fetch-happen@15.0.3: dependencies: - '@npmcli/agent': 2.2.0 - cacache: 18.0.0 - http-cache-semantics: 4.1.1 - is-lambda: 1.0.1 + '@npmcli/agent': 4.0.0 + cacache: 20.0.3 + http-cache-semantics: 4.2.0 minipass: 7.1.2 - minipass-fetch: 3.0.3 + minipass-fetch: 5.0.0 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - negotiator: 0.6.3 + negotiator: 1.0.0 + proc-log: 6.1.0 promise-retry: 2.0.1 - ssri: 10.0.4 + ssri: 13.0.0 transitivePeerDependencies: - supports-color @@ -20198,132 +20451,140 @@ snapshots: map-stream@0.1.0: {} - markdown-table@3.0.3: {} + markdown-table@3.0.4: {} math-intrinsics@1.1.0: {} matter-js@0.20.0: {} - mdast-util-find-and-replace@3.0.1: + mdast-util-find-and-replace@3.0.2: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.0: + mdast-util-from-markdown@2.0.2: dependencies: - '@types/mdast': 4.0.3 - '@types/unist': 3.0.2 - decode-named-character-reference: 1.0.2 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 - micromark: 4.0.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-decode-string: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color - mdast-util-gfm-autolink-literal@2.0.0: + mdast-util-gfm-autolink-literal@2.0.1: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.1 - micromark-util-character: 2.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 - mdast-util-gfm-footnote@2.0.0: + mdast-util-gfm-footnote@2.1.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 - micromark-util-normalize-identifier: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: - supports-color mdast-util-gfm-strikethrough@2.0.0: dependencies: - '@types/mdast': 4.0.3 - mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm-table@2.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 devlop: 1.1.0 - markdown-table: 3.0.3 - mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm-task-list-item@2.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color - mdast-util-gfm@3.0.0: + mdast-util-gfm@3.1.0: dependencies: - mdast-util-from-markdown: 2.0.0 - mdast-util-gfm-autolink-literal: 2.0.0 - mdast-util-gfm-footnote: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 mdast-util-gfm-table: 2.0.0 mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-phrasing@4.1.0: dependencies: - '@types/mdast': 4.0.3 - unist-util-is: 6.0.0 + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 mdast-util-to-hast@13.2.0: dependencies: '@types/hast': 3.0.4 - '@types/mdast': 4.0.3 - '@ungap/structured-clone': 1.2.0 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.0 + micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 unist-util-visit: 5.0.0 - vfile: 6.0.1 + vfile: 6.0.3 - mdast-util-to-markdown@2.1.0: + mdast-util-to-markdown@2.1.2: dependencies: - '@types/mdast': 4.0.3 - '@types/unist': 3.0.2 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 - micromark-util-decode-string: 2.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 unist-util-visit: 5.0.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 mdn-data@2.0.28: {} - mdn-data@2.0.30: {} + mdn-data@2.12.2: {} media-typer@0.3.0: {} - meilisearch@0.49.0: {} + media-typer@1.1.0: {} + + mediabunny@1.27.2: + dependencies: + '@types/dom-mediacapture-transform': 0.1.11 + '@types/dom-webcodecs': 0.1.13 + + meilisearch@0.54.0: {} memoizerific@1.11.3: dependencies: @@ -20333,7 +20594,7 @@ snapshots: meow@9.0.0: dependencies: - '@types/minimist': 1.2.2 + '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 decamelize: 1.2.0 decamelize-keys: 1.1.1 @@ -20348,208 +20609,206 @@ snapshots: merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} methods@1.1.2: {} - mfm-js@0.24.0: + mfm-js@0.25.0: dependencies: - '@twemoji/parser': 15.0.0 + '@twemoji/parser': 16.0.0 - microformats-parser@2.0.2: + micromark-core-commonmark@2.0.3: dependencies: - parse5: 7.2.1 - - micromark-core-commonmark@2.0.0: - dependencies: - decode-named-character-reference: 1.0.2 + decode-named-character-reference: 1.2.0 devlop: 1.1.0 - micromark-factory-destination: 2.0.0 - micromark-factory-label: 2.0.0 - micromark-factory-space: 2.0.0 - micromark-factory-title: 2.0.0 - micromark-factory-whitespace: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-classify-character: 2.0.0 - micromark-util-html-tag-name: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-subtokenize: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-extension-gfm-autolink-literal@2.0.0: + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: - micromark-util-character: 2.1.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-extension-gfm-footnote@2.0.0: + micromark-extension-gfm-footnote@2.1.0: dependencies: devlop: 1.1.0 - micromark-core-commonmark: 2.0.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-extension-gfm-strikethrough@2.0.0: + micromark-extension-gfm-strikethrough@2.1.0: dependencies: devlop: 1.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-classify-character: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-extension-gfm-table@2.0.0: + micromark-extension-gfm-table@2.1.1: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 micromark-extension-gfm-tagfilter@2.0.0: dependencies: - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.2 - micromark-extension-gfm-task-list-item@2.0.1: + micromark-extension-gfm-task-list-item@2.1.0: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 micromark-extension-gfm@3.0.0: dependencies: - micromark-extension-gfm-autolink-literal: 2.0.0 - micromark-extension-gfm-footnote: 2.0.0 - micromark-extension-gfm-strikethrough: 2.0.0 - micromark-extension-gfm-table: 2.0.0 + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.0.1 - micromark-util-combine-extensions: 2.0.0 - micromark-util-types: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 - micromark-factory-destination@2.0.0: + micromark-factory-destination@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-factory-label@2.0.0: + micromark-factory-label@2.0.1: dependencies: devlop: 1.1.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-factory-space@2.0.0: + micromark-factory-space@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 - micromark-factory-title@2.0.0: + micromark-factory-title@2.0.1: dependencies: - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-factory-whitespace@2.0.0: + micromark-factory-whitespace@2.0.1: dependencies: - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-util-character@2.1.0: + micromark-util-character@2.1.1: dependencies: - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-util-chunked@2.0.0: + micromark-util-chunked@2.0.1: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-classify-character@2.0.0: + micromark-util-classify-character@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-util-combine-extensions@2.0.0: + micromark-util-combine-extensions@2.0.1: dependencies: - micromark-util-chunked: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 - micromark-util-decode-numeric-character-reference@2.0.1: + micromark-util-decode-numeric-character-reference@2.0.2: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-decode-string@2.0.0: + micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.0.2 - micromark-util-character: 2.1.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-symbol: 2.0.0 + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 - micromark-util-encode@2.0.0: {} + micromark-util-encode@2.0.1: {} - micromark-util-html-tag-name@2.0.0: {} + micromark-util-html-tag-name@2.0.1: {} - micromark-util-normalize-identifier@2.0.0: + micromark-util-normalize-identifier@2.0.1: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-resolve-all@2.0.0: + micromark-util-resolve-all@2.0.1: dependencies: - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.2 - micromark-util-sanitize-uri@2.0.0: + micromark-util-sanitize-uri@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-encode: 2.0.0 - micromark-util-symbol: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - micromark-util-subtokenize@2.0.0: + micromark-util-subtokenize@2.1.0: dependencies: devlop: 1.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - micromark-util-symbol@2.0.0: {} + micromark-util-symbol@2.0.1: {} - micromark-util-types@2.0.0: {} + micromark-util-types@2.0.2: {} - micromark@4.0.0: + micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) - decode-named-character-reference: 1.0.2 + debug: 4.4.3(supports-color@10.2.2) + decode-named-character-reference: 1.2.0 devlop: 1.1.0 - micromark-core-commonmark: 2.0.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-combine-extensions: 2.0.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-encode: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-subtokenize: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 transitivePeerDependencies: - supports-color @@ -20560,10 +20819,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.6.0: {} @@ -20582,33 +20847,33 @@ snapshots: minimalistic-assert@1.0.1: {} - minimatch@10.0.1: + minimatch@10.0.3: dependencies: - brace-expansion: 2.0.1 + '@isaacs/brace-expansion': 5.0.0 - minimatch@3.0.8: + minimatch@10.1.1: dependencies: - brace-expansion: 1.1.11 + '@isaacs/brace-expansion': 5.0.0 minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 - minimatch@5.1.2: + minimatch@5.1.6: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.1: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.3: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.4: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist-options@4.1.0: dependencies: @@ -20618,15 +20883,15 @@ snapshots: minimist@1.2.8: {} - minipass-collect@1.0.2: + minipass-collect@2.0.1: dependencies: - minipass: 3.3.6 + minipass: 7.1.2 - minipass-fetch@3.0.3: + minipass-fetch@5.0.0: dependencies: - minipass: 5.0.0 + minipass: 7.1.2 minipass-sized: 1.0.3 - minizlib: 2.1.2 + minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 @@ -20646,7 +20911,8 @@ snapshots: dependencies: yallist: 4.0.0 - minipass@5.0.0: {} + minipass@5.0.0: + optional: true minipass@7.1.2: {} @@ -20654,92 +20920,82 @@ snapshots: dependencies: minipass: 3.3.6 yallist: 4.0.0 + optional: true - minizlib@3.0.1: + minizlib@3.1.0: dependencies: minipass: 7.1.2 - rimraf: 5.0.10 - - mkdirp-classic@0.5.3: - optional: true mkdirp@0.5.6: dependencies: minimist: 1.2.8 - mkdirp@1.0.4: {} - - mkdirp@3.0.1: {} - - mnemonist@0.40.0: - dependencies: - obliterator: 2.0.4 + mkdirp@1.0.4: + optional: true mock-socket@9.3.1: {} - module-details-from-path@1.0.3: {} + module-details-from-path@1.0.4: {} ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} - ms@3.0.0-canary.1: {} + ms@3.0.0-canary.202508261828: {} - msgpackr-extract@3.0.2: + msgpackr-extract@3.0.3: dependencies: - node-gyp-build-optional-packages: 5.0.7 + node-gyp-build-optional-packages: 5.2.2 optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.11.2: + msgpackr@1.11.5: optionalDependencies: - msgpackr-extract: 3.0.2 + msgpackr-extract: 3.0.3 - msw-storybook-addon@2.0.4(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3)): + msw-storybook-addon@2.0.6(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3)): dependencies: is-node-process: 1.2.0 - msw: 2.7.3(@types/node@22.14.0)(typescript@5.8.3) + msw: 2.12.6(@types/node@24.10.4)(typescript@5.9.3) - msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3): + msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3): dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.0.2(@types/node@22.14.0) - '@mswjs/interceptors': 0.37.5 + '@inquirer/confirm': 5.1.21(@types/node@24.10.4) + '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.4 - graphql: 16.8.1 - headers-polyfill: 4.0.2 + '@types/statuses': 2.0.6 + cookie: 1.0.2 + graphql: 16.12.0 + headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 path-to-regexp: 6.3.0 picocolors: 1.1.1 + rettime: 0.7.0 + statuses: 2.0.2 strict-event-emitter: 0.5.1 - type-fest: 4.26.1 + tough-cookie: 6.0.0 + type-fest: 5.2.0 + until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - '@types/node' muggle-string@0.4.1: {} - multer@1.4.4-lts.1: + multer@2.0.2: dependencies: append-field: 1.0.0 busboy: 1.6.0 - concat-stream: 1.6.2 + concat-stream: 2.0.0 mkdirp: 0.5.6 object-assign: 4.1.1 type-is: 1.6.18 @@ -20747,22 +21003,13 @@ snapshots: mute-stream@2.0.0: {} - mylas@2.1.13: {} + mylas@2.1.14: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 + nan@2.24.0: {} - nan@2.20.0: {} + nanoid@3.3.11: {} - nanoid@3.3.8: {} - - nanoid@5.1.5: {} - - napi-build-utils@2.0.0: - optional: true + nanoid@5.1.6: {} natural-compare@1.4.0: {} @@ -20772,44 +21019,32 @@ snapshots: dependencies: debug: 3.2.7(supports-color@8.1.1) iconv-lite: 0.4.24 - sax: 1.2.4 + sax: 1.4.3 transitivePeerDependencies: - supports-color negotiator@0.6.3: {} + negotiator@1.0.0: {} + nested-property@4.0.0: {} netmask@2.0.2: {} - nice-napi@1.0.2: - dependencies: - node-addon-api: 3.2.1 - node-gyp-build: 4.6.0 - optional: true - nise@6.1.1: dependencies: '@sinonjs/commons': 3.0.1 '@sinonjs/fake-timers': 13.0.5 '@sinonjs/text-encoding': 0.7.3 just-extend: 6.2.0 - path-to-regexp: 8.2.0 + path-to-regexp: 8.3.0 - node-abi@3.62.0: + node-abi@3.85.0: dependencies: - semver: 7.6.3 - - node-abi@3.74.0: - dependencies: - semver: 7.7.1 - optional: true + semver: 7.7.3 node-abort-controller@3.1.1: {} - node-addon-api@3.2.1: - optional: true - node-addon-api@7.1.1: optional: true @@ -20826,102 +21061,106 @@ snapshots: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 + optional: true node-fetch@3.3.2: dependencies: - data-uri-to-buffer: 4.0.0 + data-uri-to-buffer: 4.0.1 fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-gyp-build-optional-packages@5.0.7: + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 optional: true - node-gyp-build@4.6.0: + node-gyp-build@4.8.4: optional: true - node-gyp@10.2.0: + node-gyp@12.1.0: dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.1 - glob: 10.3.10 + exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 13.0.0 - nopt: 7.2.0 - proc-log: 4.2.0 - semver: 7.6.3 - tar: 6.2.1 - which: 4.0.0 + make-fetch-happen: 15.0.3 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.3 + tar: 7.5.2 + tinyglobby: 0.2.15 + which: 6.0.0 transitivePeerDependencies: - supports-color + node-html-parser@7.0.1: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + node-int64@0.4.0: {} - node-releases@2.0.19: {} + node-releases@2.0.27: {} - nodemailer@6.10.0: {} + nodemailer@7.0.12: {} nodemon@3.0.2: dependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 debug: 4.4.0(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 - semver: 7.7.1 + semver: 7.7.3 simple-update-notifier: 2.0.0 supports-color: 5.5.0 - touch: 3.1.0 + touch: 3.1.1 undefsafe: 2.0.5 - nodemon@3.1.9: + nodemon@3.1.11: dependencies: - chokidar: 4.0.3 - debug: 4.3.7(supports-color@5.5.0) + chokidar: 5.0.0 + debug: 4.4.3(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 - semver: 7.6.3 + semver: 7.7.3 simple-update-notifier: 2.0.0 supports-color: 5.5.0 - touch: 3.1.0 + touch: 3.1.1 undefsafe: 2.0.5 nofilter@3.1.0: {} - nopt@1.0.10: - dependencies: - abbrev: 1.1.1 - nopt@5.0.0: dependencies: abbrev: 1.1.1 optional: true - nopt@6.0.0: - dependencies: - abbrev: 1.1.1 - - nopt@7.2.0: + nopt@7.2.1: dependencies: abbrev: 2.0.0 + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 - semver: 5.7.1 + resolve: 1.22.11 + semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.15.1 - semver: 7.6.3 + is-core-module: 2.16.1 + semver: 7.7.3 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} - normalize-url@8.0.1: {} + normalize-url@8.1.0: {} npm-run-path@4.0.1: dependencies: @@ -20953,7 +21192,7 @@ snapshots: dependencies: boolbase: 1.0.0 - nwsapi@2.2.16: {} + nwsapi@2.2.23: {} oauth2orize-pkce@0.1.2: {} @@ -20965,61 +21204,51 @@ snapshots: transitivePeerDependencies: - supports-color - oauth@0.10.2: {} - object-assign@4.1.1: {} - object-inspect@1.13.2: {} - object-inspect@1.13.4: {} - object-is@1.1.5: + object-is@1.1.6: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 + call-bind: 1.0.8 + define-properties: 1.2.1 object-keys@1.1.1: {} - object.assign@4.1.4: + object.assign@4.1.7: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - has-symbols: 1.0.3 - object-keys: 1.1.1 - - object.assign@4.1.5: - dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - has-symbols: 1.0.3 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 object-keys: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 - object.values@1.2.0: + object.values@1.2.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 - obliterator@2.0.4: {} + oblivious-set@2.0.0: {} - oblivious-set@1.4.0: {} + obug@2.1.1: {} - obuf@1.1.2: {} - - on-exit-leak-free@2.1.0: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: dependencies: @@ -21037,30 +21266,31 @@ snapshots: dependencies: mimic-fn: 4.0.0 - oniguruma-parser@0.5.4: {} + oniguruma-parser@0.12.1: {} - oniguruma-to-es@4.1.0: + oniguruma-to-es@4.3.4: dependencies: - emoji-regex-xs: 1.0.0 - oniguruma-parser: 0.5.4 + oniguruma-parser: 0.12.1 regex: 6.0.1 regex-recursion: 6.0.2 - open@8.4.2: + open@10.2.0: dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 openapi-types@12.1.3: {} - openapi-typescript@6.7.6: + openapi-typescript@7.10.1(typescript@5.9.3): dependencies: + '@redocly/openapi-core': 1.34.5(supports-color@10.2.2) ansi-colors: 4.1.3 - fast-glob: 3.3.3 - js-yaml: 4.1.0 - supports-color: 9.4.0 - undici: 5.28.5 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 yargs-parser: 21.1.1 optionator@0.9.4: @@ -21076,12 +21306,18 @@ snapshots: ospath@1.2.2: {} - otpauth@9.4.0: + otpauth@9.4.1: dependencies: - '@noble/hashes': 1.7.1 + '@noble/hashes': 1.8.0 outvariant@1.4.3: {} + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-cancelable@3.0.0: {} p-cancelable@4.0.1: {} @@ -21108,6 +21344,8 @@ snapshots: dependencies: aggregate-error: 3.1.0 + p-map@7.0.3: {} + p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 @@ -21119,7 +21357,7 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} parent-module@1.0.1: dependencies: @@ -21127,35 +21365,38 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 - error-ex: 1.3.2 + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.27.1 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse-ms@4.0.0: {} parse-srcset@1.0.2: {} - parse5-htmlparser2-tree-adapter@6.0.1: - dependencies: - parse5: 6.0.1 - - parse5-htmlparser2-tree-adapter@7.0.0: + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 - parse5: 7.2.1 + parse5: 7.3.0 parse5-parser-stream@7.1.2: dependencies: - parse5: 7.2.1 + parse5: 7.3.0 - parse5@5.1.1: {} - - parse5@6.0.1: {} - - parse5@7.2.1: + parse5@7.3.0: dependencies: - entities: 4.5.0 + entities: 6.0.1 + + parse5@8.0.0: + dependencies: + entities: 6.0.1 + optional: true parseurl@1.3.3: {} @@ -21176,53 +21417,43 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-scurry@2.0.0: + path-scurry@2.0.1: dependencies: - lru-cache: 11.0.0 + lru-cache: 11.2.4 minipass: 7.1.2 - path-to-regexp@0.1.10: {} - path-to-regexp@0.1.12: {} path-to-regexp@6.3.0: {} - path-to-regexp@8.2.0: {} + path-to-regexp@8.3.0: {} path-type@4.0.0: {} pathe@2.0.3: {} - pathval@2.0.0: {} + pathval@2.0.1: {} pause-stream@0.0.11: dependencies: through: 2.3.8 - peek-readable@5.3.1: {} - pend@1.2.0: {} performance-now@2.1.0: {} - pg-cloudflare@1.1.1: + pg-cloudflare@1.2.7: optional: true - pg-connection-string@2.7.0: {} + pg-connection-string@2.9.1: {} pg-int8@1.0.1: {} - pg-numeric@1.0.2: {} - - pg-pool@3.8.0(pg@8.14.1): + pg-pool@3.10.1(pg@8.16.3): dependencies: - pg: 8.14.1 + pg: 8.16.3 - pg-protocol@1.7.0: {} - - pg-protocol@1.7.1: {} - - pg-protocol@1.8.0: {} + pg-protocol@1.10.3: {} pg-types@2.2.0: dependencies: @@ -21232,29 +21463,19 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg-types@4.0.1: + pg@8.16.3: dependencies: - pg-int8: 1.0.1 - pg-numeric: 1.0.2 - postgres-array: 3.0.2 - postgres-bytea: 3.0.0 - postgres-date: 2.0.1 - postgres-interval: 3.0.0 - postgres-range: 1.1.3 - - pg@8.14.1: - dependencies: - pg-connection-string: 2.7.0 - pg-pool: 3.8.0(pg@8.14.1) - pg-protocol: 1.8.0 + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.1.1 + pg-cloudflare: 1.2.7 pgpass@1.0.5: dependencies: - split2: 4.1.0 + split2: 4.2.0 photoswipe@5.4.4: {} @@ -21262,273 +21483,241 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} - pid-port@1.0.2: + pid-port@2.0.0: dependencies: - execa: 8.0.1 + execa: 9.6.1 pify@2.3.0: {} - pino-abstract-transport@1.2.0: + pino-abstract-transport@2.0.0: dependencies: - readable-stream: 4.3.0 - split2: 4.1.0 + split2: 4.2.0 pino-std-serializers@7.0.0: {} - pino@9.2.0: + pino@10.1.0: dependencies: + '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 - fast-redact: 3.1.2 - on-exit-leak-free: 2.1.0 - pino-abstract-transport: 1.2.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 pino-std-serializers: 7.0.0 - process-warning: 3.0.0 + process-warning: 5.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 - safe-stable-stringify: 2.4.2 - sonic-boom: 4.0.1 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 thread-stream: 3.1.0 - pirates@4.0.5: {} + pirates@4.0.7: {} - piscina@4.4.0: + piscina@4.9.2: optionalDependencies: - nice-napi: 1.0.2 + '@napi-rs/nice': 1.1.1 - pkce-challenge@4.1.0: {} + pkce-challenge@5.0.1: {} pkg-dir@4.2.0: dependencies: find-up: 4.1.0 - plimit-lit@1.5.0: + plimit-lit@1.6.1: dependencies: - queue-lit: 1.5.0 + queue-lit: 1.5.2 plur@4.0.0: dependencies: irregular-plurals: 3.5.0 + pluralize@8.0.0: {} + pngjs@5.0.0: {} - pnpm@10.6.1: {} + pnpm@10.27.0: {} - polished@4.2.2: + polished@4.3.1: dependencies: - '@babel/runtime': 7.23.4 + '@babel/runtime': 7.28.4 - possible-typed-array-names@1.0.0: {} + possible-typed-array-names@1.1.0: {} - postcss-calc@10.1.0(postcss@8.5.3): + postcss-calc@10.1.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 - postcss-selector-parser: 7.0.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-colormin@7.0.2(postcss@8.5.3): + postcss-colormin@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.24.4 + browserslist: 4.28.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-convert-values@7.0.4(postcss@8.5.3): + postcss-convert-values@7.0.8(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - postcss: 8.5.3 + browserslist: 4.28.0 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-discard-comments@7.0.3(postcss@8.5.3): + postcss-discard-comments@7.0.5(postcss@8.5.6): dependencies: - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 - postcss-discard-duplicates@7.0.1(postcss@8.5.3): + postcss-discard-duplicates@7.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - postcss-discard-empty@7.0.0(postcss@8.5.3): + postcss-discard-empty@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - postcss-discard-overridden@7.0.0(postcss@8.5.3): + postcss-discard-overridden@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - postcss-merge-longhand@7.0.4(postcss@8.5.3): + postcss-merge-longhand@7.0.5(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - stylehacks: 7.0.4(postcss@8.5.3) + stylehacks: 7.0.6(postcss@8.5.6) - postcss-merge-rules@7.0.4(postcss@8.5.3): + postcss-merge-rules@7.0.7(postcss@8.5.6): dependencies: - browserslist: 4.24.4 + browserslist: 4.28.0 caniuse-api: 3.0.0 - cssnano-utils: 5.0.0(postcss@8.5.3) - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 - postcss-minify-font-values@7.0.0(postcss@8.5.3): + postcss-minify-font-values@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-gradients@7.0.0(postcss@8.5.3): + postcss-minify-gradients@7.0.1(postcss@8.5.6): dependencies: colord: 2.9.3 - cssnano-utils: 5.0.0(postcss@8.5.3) - postcss: 8.5.3 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-params@7.0.2(postcss@8.5.3): + postcss-minify-params@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - cssnano-utils: 5.0.0(postcss@8.5.3) - postcss: 8.5.3 + browserslist: 4.28.0 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-selectors@7.0.4(postcss@8.5.3): + postcss-minify-selectors@7.0.5(postcss@8.5.6): dependencies: cssesc: 3.0.0 - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 - postcss-normalize-charset@7.0.0(postcss@8.5.3): + postcss-normalize-charset@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 - postcss-normalize-display-values@7.0.0(postcss@8.5.3): + postcss-normalize-display-values@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-positions@7.0.0(postcss@8.5.3): + postcss-normalize-positions@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@7.0.0(postcss@8.5.3): + postcss-normalize-repeat-style@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-string@7.0.0(postcss@8.5.3): + postcss-normalize-string@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@7.0.0(postcss@8.5.3): + postcss-normalize-timing-functions@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@7.0.2(postcss@8.5.3): + postcss-normalize-unicode@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - postcss: 8.5.3 + browserslist: 4.28.0 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-url@7.0.0(postcss@8.5.3): + postcss-normalize-url@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@7.0.0(postcss@8.5.3): + postcss-normalize-whitespace@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-ordered-values@7.0.1(postcss@8.5.3): + postcss-ordered-values@7.0.2(postcss@8.5.6): dependencies: - cssnano-utils: 5.0.0(postcss@8.5.3) - postcss: 8.5.3 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-reduce-initial@7.0.2(postcss@8.5.3): + postcss-reduce-initial@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.24.4 + browserslist: 4.28.0 caniuse-api: 3.0.0 - postcss: 8.5.3 + postcss: 8.5.6 - postcss-reduce-transforms@7.0.0(postcss@8.5.3): + postcss-reduce-transforms@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-selector-parser@6.1.2: + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.0.0: + postcss-svgo@7.1.0(postcss@8.5.6): dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - svgo: 3.3.2 + svgo: 4.0.0 - postcss-unique-selectors@7.0.3(postcss@8.5.3): + postcss-unique-selectors@7.0.4(postcss@8.5.6): dependencies: - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 postcss-value-parser@4.2.0: {} - postcss@8.5.3: + postcss@8.5.6: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 postgres-array@2.0.0: {} - postgres-array@3.0.2: {} - postgres-bytea@1.0.0: {} - postgres-bytea@3.0.0: - dependencies: - obuf: 1.1.2 - postgres-date@1.0.7: {} - postgres-date@2.0.1: {} - postgres-interval@1.2.0: dependencies: xtend: 4.0.2 - postgres-interval@3.0.0: {} - - postgres-range@1.1.3: {} - - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.74.0 - pump: 3.0.2 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.2 - tunnel-agent: 0.6.0 - optional: true - prelude-ls@1.2.1: {} - prettier@3.5.3: {} + prettier@3.7.4: {} pretty-bytes@5.6.0: {} @@ -21542,9 +21731,9 @@ snapshots: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 - pretty-ms@9.2.0: + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -21552,7 +21741,7 @@ snapshots: dependencies: '@chainsafe/is-ip': 2.1.0 ip-regex: 5.0.0 - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 netmask: 2.0.2 probe-image-size@7.2.3: @@ -21563,7 +21752,7 @@ snapshots: transitivePeerDependencies: - supports-color - proc-log@4.2.0: {} + proc-log@6.1.0: {} process-exists@5.0.0: dependencies: @@ -21571,9 +21760,9 @@ snapshots: process-nextick-args@2.0.1: {} - process-warning@3.0.0: {} + process-warning@4.0.1: {} - process-warning@4.0.0: {} + process-warning@5.0.0: {} process@0.11.10: {} @@ -21582,8 +21771,6 @@ snapshots: promise-limit@2.7.0: {} - promise-polyfill@8.3.0: {} - promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -21598,7 +21785,7 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - property-information@7.0.0: {} + property-information@7.1.0: {} proto-list@1.2.4: {} @@ -21613,6 +21800,8 @@ snapshots: ps-list@8.1.1: {} + ps-list@9.0.0: {} + ps-tree@1.2.0: dependencies: event-stream: 3.3.4 @@ -21640,27 +21829,25 @@ snapshots: void-elements: 3.1.0 with: 7.0.2 - pug-error@2.0.0: {} - pug-error@2.1.0: {} pug-filters@4.0.0: dependencies: constantinople: 4.0.1 jstransformer: 1.0.0 - pug-error: 2.0.0 + pug-error: 2.1.0 pug-walk: 2.0.0 - resolve: 1.22.8 + resolve: 1.22.11 pug-lexer@5.0.1: dependencies: character-parser: 2.2.0 is-expression: 4.0.0 - pug-error: 2.0.0 + pug-error: 2.1.0 pug-linker@4.0.0: dependencies: - pug-error: 2.0.0 + pug-error: 2.1.0 pug-walk: 2.0.0 pug-load@3.0.0: @@ -21670,14 +21857,14 @@ snapshots: pug-parser@6.0.0: dependencies: - pug-error: 2.0.0 + pug-error: 2.1.0 token-stream: 1.0.0 pug-runtime@3.0.1: {} pug-strip-comments@2.0.0: dependencies: - pug-error: 2.0.0 + pug-error: 2.1.0 pug-walk@2.0.0: {} @@ -21692,42 +21879,42 @@ snapshots: pug-runtime: 3.0.1 pug-strip-comments: 2.0.0 - pump@3.0.0: + pump@3.0.3: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 - pump@3.0.2: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - optional: true - punycode.js@2.3.1: {} punycode@2.3.1: {} - pure-rand@6.0.0: {} + pure-rand@6.1.0: {} - pvtsutils@1.3.5: + pvtsutils@1.3.6: dependencies: tslib: 2.8.1 - pvutils@1.1.3: {} + pvutils@1.1.5: {} + + qr-code-styling@1.9.2: + dependencies: + qrcode-generator: 1.5.2 + + qr-scanner@1.4.2: + dependencies: + '@types/offscreencanvas': 2019.7.3 + + qrcode-generator@1.5.2: {} qrcode@1.5.4: dependencies: - dijkstrajs: 1.0.2 + dijkstrajs: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 qs@6.13.0: dependencies: - side-channel: 1.0.6 - - qs@6.13.1: - dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 qs@6.14.0: dependencies: @@ -21735,12 +21922,10 @@ snapshots: querystringify@2.2.0: {} - queue-lit@1.5.0: {} + queue-lit@1.5.2: {} queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - quick-format-unescaped@4.0.4: {} quick-lru@4.0.1: {} @@ -21762,62 +21947,54 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@3.0.0: + raw-body@3.0.1: dependencies: bytes: 3.1.2 http-errors: 2.0.0 - iconv-lite: 0.6.3 + iconv-lite: 0.7.0 unpipe: 1.0.0 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - optional: true - - rdf-canonize@3.4.0: + rdf-canonize@5.0.0: dependencies: setimmediate: 1.0.5 - re2@1.21.4(patch_hash=018babd22b7ce951bcd10d6246f1e541a7ac7ba212f7fa8985e774ece67d08e1): + re2@1.23.0: dependencies: - install-artifact-from-github: 1.3.5 - nan: 2.20.0 - node-gyp: 10.2.0 + install-artifact-from-github: 1.4.0 + nan: 2.24.0 + node-gyp: 12.1.0 transitivePeerDependencies: - supports-color - react-docgen-typescript@2.2.2(typescript@5.8.3): + react-docgen-typescript@2.4.0(typescript@5.9.3): dependencies: - typescript: 5.8.3 + typescript: 5.9.3 - react-docgen@7.0.1: + react-docgen@8.0.2: dependencies: - '@babel/core': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.25.6 - '@types/babel__core': 7.20.0 - '@types/babel__traverse': 7.20.0 + '@babel/core': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 '@types/doctrine': 0.0.9 - '@types/resolve': 1.20.3 + '@types/resolve': 1.20.6 doctrine: 3.0.0 - resolve: 1.22.8 - strip-indent: 4.0.0 + resolve: 1.22.11 + strip-indent: 4.1.1 transitivePeerDependencies: - supports-color - react-dom@19.1.0(react@19.1.0): + react-dom@19.2.3(react@19.2.3): dependencies: - react: 19.1.0 - scheduler: 0.26.0 + react: 19.2.3 + scheduler: 0.27.0 react-is@17.0.2: {} - react-is@18.2.0: {} + react-is@18.3.1: {} - react@19.1.0: {} + react@19.2.3: {} read-pkg-up@7.0.1: dependencies: @@ -21827,12 +22004,12 @@ snapshots: read-pkg@5.2.0: dependencies: - '@types/normalize-package-data': 2.4.1 + '@types/normalize-package-data': 2.4.4 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 - readable-stream@2.3.7: + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -21855,22 +22032,23 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readable-stream@4.3.0: + readable-stream@4.7.0: dependencies: abort-controller: 3.0.0 buffer: 6.0.3 events: 3.3.0 process: 0.11.10 + string_decoder: 1.3.0 - readdir-glob@1.1.2: + readdir-glob@1.1.3: dependencies: - minimatch: 5.1.2 + minimatch: 5.1.6 - readdirp@4.1.2: {} + readdirp@5.0.0: {} real-require@0.2.0: {} - recast@0.23.6: + recast@0.23.11: dependencies: ast-types: 0.16.1 esprima: 4.0.1 @@ -21887,21 +22065,24 @@ snapshots: redis-errors@1.2.0: {} - redis-info@3.1.0: - dependencies: - lodash: 4.17.21 - - redis-lock@0.1.4: {} - redis-parser@3.0.0: dependencies: redis-errors: 1.2.0 reflect-metadata@0.2.2: {} - regenerator-runtime@0.13.11: {} + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 - regenerator-runtime@0.14.0: {} + regenerator-runtime@0.13.11: {} regex-recursion@6.0.2: dependencies: @@ -21913,44 +22094,40 @@ snapshots: dependencies: regex-utilities: 2.3.0 - regexp.prototype.flags@1.5.0: + regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - functions-have-names: 1.2.3 - - regexp.prototype.flags@1.5.2: - dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 set-function-name: 2.0.2 - remark-gfm@4.0.0: + remark-gfm@4.0.1: dependencies: - '@types/mdast': 4.0.3 - mdast-util-gfm: 3.0.0 + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 micromark-extension-gfm: 3.0.0 remark-parse: 11.0.0 remark-stringify: 11.0.0 - unified: 11.0.4 + unified: 11.0.5 transitivePeerDependencies: - supports-color remark-parse@11.0.0: dependencies: - '@types/mdast': 4.0.3 - mdast-util-from-markdown: 2.0.0 - micromark-util-types: 2.0.0 - unified: 11.0.4 + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 transitivePeerDependencies: - supports-color remark-stringify@11.0.0: dependencies: - '@types/mdast': 4.0.3 - mdast-util-to-markdown: 2.1.0 - unified: 11.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 rename@1.0.4: dependencies: @@ -21960,17 +22137,16 @@ snapshots: request-progress@3.0.0: dependencies: - throttleit: 1.0.0 + throttleit: 1.0.1 require-directory@2.1.1: {} require-from-string@2.0.2: {} - require-in-the-middle@7.3.0: + require-in-the-middle@8.0.1: dependencies: - debug: 4.4.0(supports-color@5.5.0) - module-details-from-path: 1.0.3 - resolve: 1.22.8 + debug: 4.4.3(supports-color@10.2.2) + module-details-from-path: 1.0.4 transitivePeerDependencies: - supports-color @@ -21990,11 +22166,11 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve.exports@2.0.0: {} + resolve.exports@2.0.3: {} - resolve@1.22.8: + resolve@1.22.11: dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -22002,6 +22178,10 @@ snapshots: dependencies: lowercase-keys: 3.0.0 + responselike@4.0.2: + dependencies: + lowercase-keys: 3.0.0 + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 @@ -22011,7 +22191,9 @@ snapshots: retry@0.12.0: {} - reusify@1.0.4: {} + rettime@0.7.0: {} + + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -22025,43 +22207,51 @@ snapshots: glob: 7.2.3 optional: true - rimraf@5.0.10: + rollup@4.54.0: dependencies: - glob: 10.3.10 - - rollup@4.39.0: - dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.39.0 - '@rollup/rollup-android-arm64': 4.39.0 - '@rollup/rollup-darwin-arm64': 4.39.0 - '@rollup/rollup-darwin-x64': 4.39.0 - '@rollup/rollup-freebsd-arm64': 4.39.0 - '@rollup/rollup-freebsd-x64': 4.39.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 - '@rollup/rollup-linux-arm-musleabihf': 4.39.0 - '@rollup/rollup-linux-arm64-gnu': 4.39.0 - '@rollup/rollup-linux-arm64-musl': 4.39.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-musl': 4.39.0 - '@rollup/rollup-linux-s390x-gnu': 4.39.0 - '@rollup/rollup-linux-x64-gnu': 4.39.0 - '@rollup/rollup-linux-x64-musl': 4.39.0 - '@rollup/rollup-win32-arm64-msvc': 4.39.0 - '@rollup/rollup-win32-ia32-msvc': 4.39.0 - '@rollup/rollup-win32-x64-msvc': 4.39.0 + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 - rrweb-cssom@0.8.0: {} + router@2.2.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color rss-parser@3.13.0: dependencies: entities: 2.2.0 xml2js: 0.5.0 + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -22070,72 +22260,65 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.0.0: + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - - safe-array-concat@1.1.2: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 isarray: 2.0.5 safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} - safe-regex-test@1.0.0: + safe-push-apply@1.0.0: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-regex: 1.1.4 - - safe-regex-test@1.0.3: - dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - is-regex: 1.1.4 + isarray: 2.0.5 - safe-regex2@4.0.0: + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex2@5.0.0: dependencies: ret: 0.5.0 - safe-stable-stringify@2.4.2: {} + safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} - sanitize-html@2.15.0: + sanitize-html@2.17.0: dependencies: - deepmerge: 4.2.2 + deepmerge: 4.3.1 escape-string-regexp: 4.0.0 - htmlparser2: 8.0.1 + htmlparser2: 8.0.2 is-plain-object: 5.0.0 parse-srcset: 1.0.2 - postcss: 8.5.3 + postcss: 8.5.6 - sass@1.86.3: + sass@1.97.1: dependencies: - chokidar: 4.0.3 - immutable: 5.0.3 + chokidar: 5.0.0 + immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: - '@parcel/watcher': 2.5.0 + '@parcel/watcher': 2.5.1 - sax@1.2.4: {} + sax@1.4.3: {} saxes@6.0.0: dependencies: xmlchars: 2.2.0 - scheduler@0.26.0: {} + scheduler@0.27.0: {} secure-json-parse@2.7.0: {} - secure-json-parse@3.0.2: {} + secure-json-parse@4.1.0: {} seedrandom@3.0.5: {} @@ -22147,9 +22330,9 @@ snapshots: semver-truncate@3.0.0: dependencies: - semver: 7.6.3 + semver: 7.7.3 - semver@5.7.1: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -22157,9 +22340,7 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.6.3: {} - - semver@7.7.1: {} + semver@7.7.3: {} send@0.19.0: dependencies: @@ -22179,6 +22360,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -22188,16 +22385,25 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} - set-cookie-parser@2.6.0: {} + set-cookie-parser@2.7.1: {} set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -22208,41 +22414,47 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} - sha.js@2.4.11: + sha.js@2.4.12: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 - sharp@0.34.1: + sharp@0.33.5: dependencies: color: 4.2.3 - detect-libc: 2.0.3 - semver: 7.7.1 + detect-libc: 2.1.2 + semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.1 - '@img/sharp-darwin-x64': 0.34.1 - '@img/sharp-libvips-darwin-arm64': 1.1.0 - '@img/sharp-libvips-darwin-x64': 1.1.0 - '@img/sharp-libvips-linux-arm': 1.1.0 - '@img/sharp-libvips-linux-arm64': 1.1.0 - '@img/sharp-libvips-linux-ppc64': 1.1.0 - '@img/sharp-libvips-linux-s390x': 1.1.0 - '@img/sharp-libvips-linux-x64': 1.1.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 - '@img/sharp-libvips-linuxmusl-x64': 1.1.0 - '@img/sharp-linux-arm': 0.34.1 - '@img/sharp-linux-arm64': 0.34.1 - '@img/sharp-linux-s390x': 0.34.1 - '@img/sharp-linux-x64': 0.34.1 - '@img/sharp-linuxmusl-arm64': 0.34.1 - '@img/sharp-linuxmusl-x64': 0.34.1 - '@img/sharp-wasm32': 0.34.1 - '@img/sharp-win32-ia32': 0.34.1 - '@img/sharp-win32-x64': 0.34.1 + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 shebang-command@2.0.0: dependencies: @@ -22250,19 +22462,17 @@ snapshots: shebang-regex@3.0.0: {} - shiki@3.2.2: + shiki@3.20.0: dependencies: - '@shikijs/core': 3.2.2 - '@shikijs/engine-javascript': 3.2.2 - '@shikijs/engine-oniguruma': 3.2.2 - '@shikijs/langs': 3.2.2 - '@shikijs/themes': 3.2.2 - '@shikijs/types': 3.2.2 + '@shikijs/core': 3.20.0 + '@shikijs/engine-javascript': 3.20.0 + '@shikijs/engine-oniguruma': 3.20.0 + '@shikijs/langs': 3.20.0 + '@shikijs/themes': 3.20.0 + '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - shimmer@1.2.1: {} - side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -22272,24 +22482,17 @@ snapshots: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.0.6: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 - side-channel@1.1.0: dependencies: es-errors: 1.3.0 @@ -22304,38 +22507,28 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: - optional: true - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - optional: true - simple-oauth2@5.1.0: dependencies: - '@hapi/hoek': 11.0.4 - '@hapi/wreck': 18.0.1 - debug: 4.3.5 - joi: 17.11.0 + '@hapi/hoek': 11.0.7 + '@hapi/wreck': 18.1.0 + debug: 4.4.3(supports-color@10.2.2) + joi: 17.13.3 transitivePeerDependencies: - supports-color - simple-swizzle@0.2.2: + simple-swizzle@0.2.4: dependencies: - is-arrayish: 0.3.2 + is-arrayish: 0.3.4 simple-update-notifier@2.0.0: dependencies: - semver: 7.6.3 + semver: 7.7.3 sinon@18.0.1: dependencies: '@sinonjs/commons': 3.0.1 '@sinonjs/fake-timers': 11.2.2 - '@sinonjs/samsam': 8.0.0 + '@sinonjs/samsam': 8.0.3 diff: 5.2.0 nise: 6.1.1 supports-color: 7.2.0 @@ -22415,20 +22608,20 @@ snapshots: smart-buffer@4.2.0: {} - socks-proxy-agent@8.0.2: + socks-proxy-agent@8.0.5: dependencies: - agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) - socks: 2.7.1 + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + socks: 2.8.7 transitivePeerDependencies: - supports-color - socks@2.7.1: + socks@2.8.7: dependencies: - ip: 2.0.1 + ip-address: 10.0.1 smart-buffer: 4.2.0 - sonic-boom@4.0.1: + sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -22440,8 +22633,6 @@ snapshots: dependencies: is-plain-obj: 1.1.0 - sortablejs@1.14.0: {} - source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -22456,25 +22647,25 @@ snapshots: source-map@0.6.1: {} - source-map@0.7.4: {} + source-map@0.7.6: {} space-separated-tokens@2.0.2: {} - spdx-correct@3.1.1: + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.12 + spdx-license-ids: 3.0.22 - spdx-exceptions@2.3.0: {} + spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.12 + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 - spdx-license-ids@3.0.12: {} + spdx-license-ids@3.0.22: {} - split2@4.1.0: {} + split2@4.2.0: {} split@0.3.3: dependencies: @@ -22484,19 +22675,7 @@ snapshots: sprintf-js@1.1.3: {} - sql-highlight@6.0.0: {} - - sshpk@1.17.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 + sql-highlight@6.1.0: {} sshpk@1.18.0: dependencies: @@ -22510,9 +22689,9 @@ snapshots: safer-buffer: 2.1.2 tweetnacl: 0.14.5 - ssri@10.0.4: + ssri@13.0.0: dependencies: - minipass: 5.0.0 + minipass: 7.1.2 stack-utils@2.0.6: dependencies: @@ -22522,61 +22701,64 @@ snapshots: standard-as-callback@2.1.0: {} - start-server-and-test@2.0.10: + start-server-and-test@2.1.3: dependencies: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@10.2.2) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 - wait-on: 8.0.2(debug@4.4.0) - transitivePeerDependencies: - - supports-color - - start-server-and-test@2.0.11: - dependencies: - arg: 5.0.2 - bluebird: 3.7.2 - check-more-types: 2.24.0 - debug: 4.4.0(supports-color@5.5.0) - execa: 5.1.1 - lazy-ass: 1.6.0 - ps-tree: 1.2.0 - wait-on: 8.0.3(debug@4.4.0) + wait-on: 9.0.3(debug@4.4.3) transitivePeerDependencies: - supports-color statuses@2.0.1: {} - std-env@3.9.0: {} + statuses@2.0.2: {} - stop-iteration-iterator@1.0.0: - dependencies: - internal-slot: 1.0.5 + std-env@3.10.0: {} - storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/components@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/core-events@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/manager-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/preview-api@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/theming@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/types@8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + stop-iteration-iterator@1.1.0: dependencies: - '@storybook/blocks': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/components': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/core-events': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/manager-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/preview-api': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/theming': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) - '@storybook/types': 8.6.12(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) + 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(d02eada84d4e66c2dd831f7184cb2a49): + dependencies: + '@storybook/blocks': 8.6.15(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/components': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/core-events': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/manager-api': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/preview-api': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/theming': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) + '@storybook/types': 8.6.15(storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)) optionalDependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5): + storybook@10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6): dependencies: - '@storybook/core': 8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(storybook@8.6.12(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(utf-8-validate@6.0.5) + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@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/spy': 3.2.4 + esbuild: 0.27.2 + open: 10.2.0 + recast: 0.23.11 + semver: 7.7.3 + use-sync-external-store: 1.6.0(react@19.2.3) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - prettier: 3.5.3 + prettier: 3.7.4 transitivePeerDependencies: + - '@testing-library/dom' - bufferutil - - supports-color + - react + - react-dom - utf-8-validate stream-browserify@3.0.0: @@ -22596,16 +22778,20 @@ snapshots: streamsearch@1.1.0: {} - streamx@2.15.0: + streamx@2.23.0: dependencies: - fast-fifo: 1.3.0 - queue-tick: 1.0.1 + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a strict-event-emitter-types@2.0.0: {} strict-event-emitter@0.5.1: {} - string-argv@0.3.1: {} + string-argv@0.3.2: {} string-length@4.0.2: dependencies: @@ -22622,44 +22808,36 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 - string.prototype.trim@1.2.7: + string-width@7.2.0: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 - string.prototype.trim@1.2.9: + string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 - string.prototype.trimend@1.0.6: + string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 - - string.prototype.trimend@1.0.8: - dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.0.0 - - string.prototype.trimstart@1.0.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.1 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string_decoder@1.1.1: dependencies: @@ -22682,9 +22860,9 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} @@ -22705,27 +22883,44 @@ snapshots: dependencies: min-indent: 1.0.1 - strip-indent@4.0.0: - dependencies: - min-indent: 1.0.1 - - strip-json-comments@2.0.1: - optional: true + strip-indent@4.1.1: {} strip-json-comments@3.1.1: {} - strnum@1.0.5: {} + strnum@2.1.1: {} - strtok3@9.0.1: + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 - peek-readable: 5.3.1 - stylehacks@7.0.4(postcss@8.5.3): + stylehacks@7.0.6(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 + browserslist: 4.28.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + + superagent@10.2.3: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3(supports-color@10.2.2) + fast-safe-stringify: 2.1.1 + form-data: 4.0.5 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + transitivePeerDependencies: + - supports-color + + supertest@7.1.4: + dependencies: + methods: 1.1.2 + superagent: 10.2.3 + transitivePeerDependencies: + - supports-color + + supports-color@10.2.2: {} supports-color@5.5.0: dependencies: @@ -22739,8 +22934,6 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@9.4.0: {} - supports-hyperlinks@2.3.0: dependencies: has-flag: 4.0.0 @@ -22748,48 +22941,30 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svgo@3.3.2: + svgo@4.0.0: dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 5.1.0 - css-tree: 2.3.1 - css-what: 6.1.0 + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 + sax: 1.4.3 symbol-tree@3.2.4: {} - systeminformation@5.25.11: {} + systeminformation@5.28.1: {} - tar-fs@2.1.2: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - optional: true - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - optional: true - - tar-stream@3.1.6: - dependencies: - b4a: 1.6.4 - fast-fifo: 1.3.0 - streamx: 2.15.0 + tagged-tag@1.0.0: {} tar-stream@3.1.7: dependencies: - b4a: 1.6.4 - fast-fifo: 1.3.0 - streamx: 2.15.0 + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a tar@6.2.1: dependencies: @@ -22799,24 +22974,24 @@ snapshots: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + optional: true - tar@7.4.3: + tar@7.5.2: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.1 - mkdirp: 3.0.1 + minizlib: 3.1.0 yallist: 5.0.0 taskkill@5.0.0: dependencies: execa: 6.1.0 - terser@5.39.0: + terser@5.44.1: dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.14.1 + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -22826,31 +23001,23 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - test-exclude@7.0.1: + text-decoder@1.2.3: dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 - minimatch: 9.0.4 + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a textarea-caret@3.1.0: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - thread-stream@3.1.0: dependencies: real-require: 0.2.0 - three@0.175.0: {} + three@0.182.0: {} throttle-debounce@5.0.2: {} - throttleit@1.0.0: {} + throttleit@1.0.1: {} through@2.3.8: {} @@ -22860,34 +23027,46 @@ snapshots: tinycolor2@1.6.0: {} - tinyexec@0.3.2: {} + tinyexec@1.0.2: {} - tinyglobby@0.2.12: + tinyglobby@0.2.15: dependencies: - fdir: 6.4.3(picomatch@4.0.2) - picomatch: 4.0.2 - - tinypool@1.0.2: {} + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 tinyrainbow@1.2.0: {} tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@3.0.2: {} - tldts-core@6.1.61: {} + tinyspy@4.0.4: {} - tldts@6.1.61: + tldts-core@6.1.86: {} + + tldts-core@7.0.18: {} + + tldts@6.1.86: dependencies: - tldts-core: 6.1.61 + tldts-core: 6.1.86 - tmp@0.2.3: {} + tldts@7.0.18: + dependencies: + tldts-core: 7.0.18 + + tmp@0.2.5: {} tmpl@1.0.5: {} - to-data-view@1.1.0: {} + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 - to-fast-properties@2.0.0: {} + to-data-view@1.1.0: {} to-regex-range@5.0.1: dependencies: @@ -22899,14 +23078,13 @@ snapshots: token-stream@1.0.0: {} - token-types@6.0.0: + token-types@6.1.1: dependencies: + '@borewit/text-codec': 0.1.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - touch@3.1.0: - dependencies: - nopt: 1.0.10 + touch@3.1.1: {} tough-cookie@4.1.4: dependencies: @@ -22915,16 +23093,25 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 - tough-cookie@5.0.0: + tough-cookie@5.1.2: dependencies: - tldts: 6.1.61 + tldts: 6.1.86 + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.18 tr46@0.0.3: {} - tr46@5.0.0: + tr46@3.0.0: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + optional: true + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -22937,13 +23124,9 @@ snapshots: dependencies: typescript: 5.3.3 - ts-api-utils@2.0.1(typescript@5.8.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.8.2 - - ts-api-utils@2.0.1(typescript@5.8.3): - dependencies: - typescript: 5.8.3 + typescript: 5.9.3 ts-case-convert@2.1.0: {} @@ -22953,7 +23136,7 @@ snapshots: dependencies: jest-resolve: 29.7.0 - ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.11)(jest@29.7.0(@types/node@20.11.17))(typescript@5.3.3): + ts-jest@29.1.2(@babel/core@7.28.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(esbuild@0.19.11)(jest@29.7.0(@types/node@20.11.17))(typescript@5.3.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -22962,26 +23145,26 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.3 typescript: 5.3.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.5 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7) + babel-jest: 29.7.0(@babel/core@7.28.5) esbuild: 0.19.11 ts-map@1.0.3: {} - tsc-alias@1.8.15: + tsc-alias@1.8.16: dependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 commander: 9.5.0 - get-tsconfig: 4.10.0 + get-tsconfig: 4.13.0 globby: 11.1.0 - mylas: 2.1.13 + mylas: 2.1.14 normalize-path: 3.0.0 - plimit-lit: 1.5.0 + plimit-lit: 1.6.1 tsconfig-paths@3.15.0: dependencies: @@ -22996,9 +23179,9 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tsd@0.31.2: + tsd@0.33.0: dependencies: - '@tsd/typescript': 5.4.5 + '@tsd/typescript': 5.9.3 eslint-formatter-pretty: 4.1.0 globby: 11.1.0 jest-diff: 29.7.0 @@ -23006,17 +23189,21 @@ snapshots: path-exists: 4.0.0 read-pkg-up: 7.0.1 - tslib@2.6.2: {} + tslib@1.14.1: {} tslib@2.8.1: {} - tsx@4.19.3: + tsx@4.21.0: dependencies: - esbuild: 0.25.2 - get-tsconfig: 4.9.0 + esbuild: 0.27.2 + get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -23029,6 +23216,8 @@ snapshots: type-detect@4.0.8: {} + type-detect@4.1.0: {} + type-fest@0.18.1: {} type-fest@0.21.3: {} @@ -23039,118 +23228,104 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.26.1: {} + type-fest@4.41.0: {} + + type-fest@5.2.0: + dependencies: + tagged-tag: 1.0.0 type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - typed-array-buffer@1.0.0: + type-is@2.0.1: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-typed-array: 1.1.13 + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 - typed-array-buffer@1.0.2: + typed-array-buffer@1.0.3: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.4 es-errors: 1.3.0 - is-typed-array: 1.1.13 + is-typed-array: 1.1.15 - typed-array-byte-length@1.0.0: + typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.13 - - typed-array-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 + call-bind: 1.0.8 + for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + has-proto: 1.2.0 + is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.0: + typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.13 - - typed-array-byte-offset@1.0.2: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 + call-bind: 1.0.8 + for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.4: + typed-array-length@1.0.7: dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - is-typed-array: 1.1.13 - - typed-array-length@1.0.6: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 + call-bind: 1.0.8 + for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 typedarray@0.0.6: {} - typeorm@0.3.22(ioredis@5.6.0)(pg@8.14.1)(reflect-metadata@0.2.2): + typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3): dependencies: '@sqltools/formatter': 1.2.5 - ansis: 3.17.0 + ansis: 4.2.0 app-root-path: 3.1.0 buffer: 6.0.3 - dayjs: 1.11.13 - debug: 4.4.0(supports-color@5.5.0) - dotenv: 16.5.0 - glob: 10.4.5 + dayjs: 1.11.19 + debug: 4.4.3(supports-color@10.2.2) + dedent: 1.7.0 + dotenv: 16.6.1 + glob: 10.5.0 reflect-metadata: 0.2.2 - sha.js: 2.4.11 - sql-highlight: 6.0.0 + sha.js: 2.4.12 + sql-highlight: 6.1.0 tslib: 2.8.1 uuid: 11.1.0 yargs: 17.7.2 optionalDependencies: - ioredis: 5.6.0 - pg: 8.14.1 + ioredis: 5.8.2 + pg: 8.16.3 transitivePeerDependencies: + - babel-plugin-macros - supports-color typescript@5.3.3: {} typescript@5.8.2: {} - typescript@5.8.3: {} + typescript@5.9.3: {} uid2@0.0.4: {} uid@2.0.2: dependencies: - '@lukeed/csprng': 1.0.1 + '@lukeed/csprng': 1.1.0 - uint8array-extras@1.4.0: {} + uint8array-extras@1.5.0: {} - ulid@2.4.0: {} + ulid@3.0.2: {} - unbox-primitive@1.0.2: + unbox-primitive@1.1.0: dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 unbzip2-stream@1.4.3: dependencies: @@ -23161,81 +23336,86 @@ snapshots: undici-types@5.26.5: {} - undici-types@6.20.0: {} - undici-types@6.21.0: {} - undici@5.28.5: - dependencies: - '@fastify/busboy': 2.1.0 + undici-types@7.16.0: {} - undici@6.19.8: {} + undici@6.22.0: {} + + undici@7.16.0: {} unicorn-magic@0.3.0: {} - unified@11.0.4: + unified@11.0.5: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 bail: 2.0.2 devlop: 1.1.0 extend: 3.0.2 is-plain-obj: 4.1.0 trough: 2.2.0 - vfile: 6.0.1 + vfile: 6.0.3 - unique-filename@3.0.0: + unique-filename@5.0.0: dependencies: - unique-slug: 4.0.0 + unique-slug: 6.0.0 - unique-slug@4.0.0: + unique-slug@6.0.0: dependencies: imurmurhash: 0.1.4 - unist-util-is@6.0.0: + unist-util-is@6.0.1: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-position@5.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-stringify-position@4.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 - unist-util-visit-parents@6.0.1: + unist-util-visit-parents@6.0.2: dependencies: - '@types/unist': 3.0.2 - unist-util-is: 6.0.0 + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 unist-util-visit@5.0.0: dependencies: - '@types/unist': 3.0.2 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 universalify@0.1.2: {} universalify@0.2.0: {} - universalify@2.0.0: {} + universalify@2.0.1: {} unload@2.4.1: {} unpipe@1.0.0: {} - unplugin@1.4.0: + unplugin@1.16.1: dependencies: - acorn: 8.14.1 - chokidar: 4.0.3 - webpack-sources: 3.2.3 - webpack-virtual-modules: 0.5.0 + acorn: 8.15.0 + webpack-virtual-modules: 0.6.2 + + unplugin@2.3.10: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + until-async@3.0.2: {} untildify@4.0.0: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.24.4 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -23248,48 +23428,46 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - utf-8-validate@6.0.5: + use-sync-external-store@1.6.0(react@19.2.3): dependencies: - node-gyp-build: 4.6.0 + react: 19.2.3 + + utf-8-validate@6.0.6: + dependencies: + node-gyp-build: 4.8.4 optional: true util-deprecate@1.0.2: {} - util@0.12.5: - dependencies: - inherits: 2.0.4 - is-arguments: 1.1.1 - is-generator-function: 1.0.10 - is-typed-array: 1.1.13 - which-typed-array: 1.1.15 - utils-merge@1.0.1: {} uuid@11.1.0: {} + uuid@13.0.0: {} + uuid@8.3.2: {} uuid@9.0.1: {} - v-code-diff@1.13.1(vue@3.5.13(typescript@5.8.3)): + v-code-diff@1.13.1(vue@3.5.26(typescript@5.9.3)): dependencies: diff: 5.2.0 diff-match-patch: 1.0.5 - highlight.js: 11.10.0 - vue: 3.5.13(typescript@5.8.3) - vue-demi: 0.14.7(vue@3.5.13(typescript@5.8.3)) + highlight.js: 11.11.1 + vue: 3.5.26(typescript@5.9.3) + vue-demi: 0.14.10(vue@3.5.26(typescript@5.9.3)) - v8-to-istanbul@9.2.0: + v8-to-istanbul@9.3.0: dependencies: - '@jridgewell/trace-mapping': 0.3.25 - '@types/istanbul-lib-coverage': 2.0.4 + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 valid-data-url@3.0.1: {} validate-npm-package-license@3.0.4: dependencies: - spdx-correct: 3.1.1 + spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 vary@1.1.2: {} @@ -23300,86 +23478,77 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vfile-message@4.0.2: + vfile-message@4.0.3: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - vfile@6.0.1: + vfile@6.0.3: dependencies: - '@types/unist': 3.0.2 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 + '@types/unist': 3.0.3 + vfile-message: 4.0.3 - vite-node@3.1.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3): + vite-plugin-glsl@1.5.5(@rollup/pluginutils@5.3.0(rollup@4.54.0))(esbuild@0.27.2)(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)): dependencies: - cac: 6.7.14 - debug: 4.4.0(supports-color@5.5.0) - es-module-lexer: 1.6.0 - pathe: 2.0.3 - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + optionalDependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + esbuild: 0.27.2 vite-plugin-turbosnap@1.0.3: {} - vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3): + vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0): dependencies: - esbuild: 0.25.2 - fdir: 6.4.3(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.3 - rollup: 4.39.0 - tinyglobby: 0.2.12 + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 24.10.4 fsevents: 2.3.3 - sass: 1.86.3 - terser: 5.39.0 - tsx: 4.19.3 + sass: 1.97.1 + terser: 5.44.1 + tsx: 4.21.0 - vitest-fetch-mock@0.4.5(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)): + vitest-fetch-mock@0.4.5(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)): dependencies: - vitest: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) - vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3): + vitest-websocket-mock@0.5.0(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)): dependencies: - '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.3))(vite@6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3)) - '@vitest/pretty-format': 3.1.1 - '@vitest/runner': 3.1.1 - '@vitest/snapshot': 3.1.1 - '@vitest/spy': 3.1.1 - '@vitest/utils': 3.1.1 - chai: 5.2.0 - debug: 4.4.0(supports-color@5.5.0) - expect-type: 1.2.1 - magic-string: 0.30.17 + '@vitest/utils': 3.2.4 + mock-socket: 9.3.1 + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) + + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(happy-dom@20.0.11)(jsdom@27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(msw@2.12.6(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 - std-env: 3.9.0 + picomatch: 4.0.3 + std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.0.2 - tinyrainbow: 2.0.0 - vite: 6.3.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) - vite-node: 3.1.1(@types/node@22.14.0)(sass@1.86.3)(terser@5.39.0)(tsx@4.19.3) + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.0(@types/node@24.10.4)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 22.14.0 - happy-dom: 17.4.4 - jsdom: 26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5) + '@opentelemetry/api': 1.9.0 + '@types/node': 24.10.4 + happy-dom: 20.0.11 + jsdom: 27.2.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - jiti - less @@ -23389,7 +23558,6 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml @@ -23400,8 +23568,8 @@ snapshots: vscode-languageclient@9.0.1: dependencies: - minimatch: 5.1.2 - semver: 7.6.3 + minimatch: 5.1.6 + semver: 7.7.3 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-protocol@3.17.5: @@ -23411,103 +23579,88 @@ snapshots: vscode-languageserver-types@3.17.5: {} - vscode-uri@3.0.8: {} + vscode-uri@3.1.0: {} - vue-component-meta@2.0.16(typescript@5.8.3): + vue-component-meta@2.2.12(typescript@5.9.3): dependencies: - '@volar/typescript': 2.2.0 - '@vue/language-core': 2.0.16(typescript@5.8.3) + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.9.3) path-browserify: 1.0.1 - vue-component-type-helpers: 2.0.16 + vue-component-type-helpers: 2.2.12 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 - vue-component-type-helpers@1.8.4: {} + vue-component-type-helpers@2.2.12: {} - vue-component-type-helpers@2.0.16: {} + vue-component-type-helpers@3.2.1: {} - vue-component-type-helpers@2.2.8: {} + vue-component-type-helpers@3.2.2: {} - vue-demi@0.14.7(vue@3.5.13(typescript@5.8.3)): + vue-demi@0.14.10(vue@3.5.26(typescript@5.9.3)): dependencies: - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.26(typescript@5.9.3) - vue-docgen-api@4.75.1(vue@3.5.13(typescript@5.8.3)): + vue-docgen-api@4.79.2(vue@3.5.26(typescript@5.9.3)): dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-sfc': 3.5.13 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 ast-types: 0.16.1 + esm-resolve: 1.0.11 hash-sum: 2.0.0 - lru-cache: 8.0.4 + lru-cache: 8.0.5 pug: 3.0.3 - recast: 0.23.6 + recast: 0.23.11 ts-map: 1.0.3 - vue: 3.5.13(typescript@5.8.3) - vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.13(typescript@5.8.3)) + vue: 3.5.26(typescript@5.9.3) + vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.26(typescript@5.9.3)) - vue-eslint-parser@10.1.3(eslint@9.22.0): + vue-eslint-parser@10.2.0(eslint@9.39.2): dependencies: - debug: 4.4.0(supports-color@5.5.0) - eslint: 9.22.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.2 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 - lodash: 4.17.21 - semver: 7.7.1 + semver: 7.7.3 transitivePeerDependencies: - supports-color - vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.13(typescript@5.8.3)): + vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.26(typescript@5.9.3)): dependencies: - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.26(typescript@5.9.3) - vue-template-compiler@2.7.14: + vue-tsc@3.2.1(typescript@5.9.3): dependencies: - de-indent: 1.0.2 - he: 1.2.0 + '@volar/typescript': 2.4.27 + '@vue/language-core': 3.2.1 + typescript: 5.9.3 - vue-tsc@2.2.8(typescript@5.8.3): + vue@3.5.26(typescript@5.9.3): dependencies: - '@volar/typescript': 2.4.11 - '@vue/language-core': 2.2.8(typescript@5.8.3) - typescript: 5.8.3 - - vue@3.5.13(typescript@5.8.3): - dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-sfc': 3.5.13 - '@vue/runtime-dom': 3.5.13 - '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.3)) - '@vue/shared': 3.5.13 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 - vuedraggable@4.1.0(vue@3.5.13(typescript@5.8.3)): + w3c-xmlserializer@4.0.0: dependencies: - sortablejs: 1.14.0 - vue: 3.5.13(typescript@5.8.3) + xml-name-validator: 4.0.0 w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + optional: true - wait-on@8.0.2(debug@4.4.0): + wait-on@9.0.3(debug@4.4.3): dependencies: - axios: 1.7.9(debug@4.4.0) - joi: 17.13.3 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.2 - transitivePeerDependencies: - - debug - - wait-on@8.0.3(debug@4.4.0): - dependencies: - axios: 1.8.4(debug@4.4.0) - joi: 17.13.3 + axios: 1.13.2(debug@4.4.3) + joi: 18.0.1 lodash: 4.17.21 minimist: 1.2.8 rxjs: 7.8.2 @@ -23520,11 +23673,15 @@ snapshots: wanakana@5.3.1: {} + wawoff2@2.0.1: + dependencies: + argparse: 2.0.1 + web-push@3.6.7: dependencies: asn1.js: 5.4.1 http_ece: 1.2.0 - https-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) jws: 4.0.0 minimist: 1.2.8 transitivePeerDependencies: @@ -23538,18 +23695,20 @@ snapshots: mime: 2.6.0 valid-data-url: 3.0.1 - web-streams-polyfill@3.2.1: {} - - web-streams-polyfill@4.0.0: - optional: true + web-streams-polyfill@3.3.3: {} webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} - webpack-sources@3.2.3: {} + webidl-conversions@8.0.1: + optional: true - webpack-virtual-modules@0.5.0: {} + webpack-virtual-modules@0.6.2: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 whatwg-encoding@3.1.1: dependencies: @@ -23559,46 +23718,62 @@ snapshots: whatwg-mimetype@4.0.0: {} - whatwg-url@14.1.0: + whatwg-url@11.0.0: dependencies: - tr46: 5.0.0 + tr46: 3.0.0 webidl-conversions: 7.0.0 + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.1 + optional: true + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.0.2: + which-boxed-primitive@1.1.1: dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 - which-collection@1.0.1: + which-builtin-type@1.2.1: dependencies: - is-map: 2.0.2 - is-set: 2.0.2 - is-weakmap: 2.0.1 - is-weakset: 2.0.2 - - which-module@2.0.0: {} - - which-typed-array@1.1.11: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.2.0 + call-bound: 1.0.4 + function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 - which-typed-array@1.1.15: + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-module@2.0.1: {} + + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -23610,7 +23785,7 @@ snapshots: dependencies: isexe: 2.0.0 - which@4.0.0: + which@6.0.0: dependencies: isexe: 3.1.1 @@ -23626,9 +23801,9 @@ snapshots: with@7.0.2: dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - assert-never: 1.2.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + assert-never: 1.4.0 babel-walk: 3.0.0-canary-5 word-wrap@1.2.5: {} @@ -23647,9 +23822,15 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -23658,24 +23839,29 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5): + ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 6.0.5 + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 xev@3.0.2: {} xml-js@1.6.11: dependencies: - sax: 1.2.4 + sax: 1.4.3 xml-name-validator@4.0.0: {} - xml-name-validator@5.0.0: {} + xml-name-validator@5.0.0: + optional: true xml2js@0.5.0: dependencies: - sax: 1.2.4 + sax: 1.4.3 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -23694,6 +23880,8 @@ snapshots: yallist@5.0.0: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 @@ -23703,6 +23891,8 @@ snapshots: yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -23713,14 +23903,14 @@ snapshots: require-main-filename: 2.0.0 set-blocking: 2.0.0 string-width: 4.2.3 - which-module: 2.0.0 + which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 yargs@16.2.0: dependencies: cliui: 7.0.4 - escalade: 3.1.1 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -23737,6 +23927,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 @@ -23749,14 +23948,14 @@ snapshots: yocto-queue@0.1.0: {} - yoctocolors-cjs@2.1.2: {} + yoctocolors-cjs@2.1.3: {} - yoctocolors@2.1.1: {} + yoctocolors@2.1.2: {} zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 compress-commons: 6.0.2 - readable-stream: 4.3.0 + readable-stream: 4.7.0 zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7e91a8dfb4..8315b045fd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,7 +2,10 @@ packages: - packages/backend - packages/frontend-shared - packages/frontend + - packages/frontend-builder - packages/frontend-embed + - packages/i18n + - packages/icons-subsetter - packages/sw - packages/misskey-js - packages/misskey-js/generator @@ -13,6 +16,7 @@ onlyBuiltDependencies: - '@nestjs/core' - '@parcel/watcher' - '@sentry/profiling-node' + - '@sentry-internal/node-cpu-profiler' - '@swc/core' - '@tensorflow/tfjs-node' - bufferutil @@ -29,3 +33,7 @@ onlyBuiltDependencies: - v-code-diff - vue-demi ignorePatchFailures: false +minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack +minimumReleaseAgeExclude: + - '@syuilo/aiscript' + - systeminformation # 脆弱性対応。そのうち消すこと diff --git a/renovate.json5 b/renovate.json5 index 395405972d..0b0f9e1ac9 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -15,11 +15,16 @@ dependencyDashboardAutoclose: true, osvVulnerabilityAlerts: true, dependencyDashboardOSVVulnerabilitySummary: 'unresolved', + minimumReleaseAge: '7 days', ignoreDeps: [ // https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458 '@typescript/lib-webworker', // https://github.com/misskey-dev/misskey/pull/15494#issuecomment-2660775258 'nsfwjs', + // https://github.com/misskey-dev/misskey/issues/15920 + 'sharp', + '@misskey-dev/sharp-read-bmp', + '@syuilo/aiscript-0-19-0', ], packageRules: [ { @@ -34,10 +39,14 @@ 'packages/frontend/**/package.json', 'packages/frontend-embed/**/package.json', 'packages/frontend-shared/**/package.json', + 'packages/frontend-builder/**/package.json', 'packages/misskey-bubble-game/**/package.json', 'packages/misskey-reversi/**/package.json', 'packages/sw/**/package.json', + 'packages/icons-subsetter/**/package.json', ], + // prevent wastage of Chromatic snapshots + rebaseWhen: 'never', }, { groupName: '[misskey-js] Update dependencies', @@ -69,6 +78,18 @@ '.node-version', ], }, + { + groupName: '[Node.js] Update dependencies', + matchManagers: ['custom.regex'], + matchPackageNames: ['node'], + }, + { + groupName: '[Node.js] Update dependencies', + matchDepNames: ['ghcr.io/devcontainers/features/node:1'], + matchFileNames: [ + '.devcontainer/**', + ], + }, { groupName: '[Docker] Update dependencies', matchFileNames: [ @@ -80,9 +101,32 @@ }, { groupName: '[devcontainer] Update dependencies', + matchDepNames: ['!ghcr.io/devcontainers/features/node:1'], matchFileNames: [ '.devcontainer/**', ], }, + { + // devcontainer (Dockerfile用の設定) + groupName: '[devcontainer] Update dependencies', + matchFileNames: ['.devcontainer/Dockerfile'], + matchDepNames: ['mcr.microsoft.com/devcontainers/javascript-node'], + allowedVersions: '/-[0-9]+-trixie$/', + + // major/minor/patch: devcontainer の semver + // build: Node メジャー + // dist: Debian codename (e.g., bullseye, bookworm)(比較には使わない) + versioning: 'regex:^(?\\d+)\\.(?\\d+)\\.(?\\d+)-(?\\d+)-(?.+)$', + }, + ], + customManagers: [ + { + customType: 'regex', + managerFilePatterns: ['/^Dockerfile$/'], + matchStrings: ['ARG NODE_VERSION=(?.*?)\\n'], + datasourceTemplate: 'docker', + depNameTemplate: 'node', + versioningTemplate: 'docker', + }, ], } diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs index 8ab341795c..1086d5a25a 100644 --- a/scripts/build-assets.mjs +++ b/scripts/build-assets.mjs @@ -6,15 +6,8 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; -import cssnano from 'cssnano'; import * as yaml from 'js-yaml'; -import postcss from 'postcss'; -import * as terser from 'terser'; - -import { build as buildLocales } from '../locales/index.js'; -import generateDTS from '../locales/generateDTS.js'; -import meta from '../package.json' with { type: "json" }; -import buildTarball from './tarball.mjs'; +import { buildTarball } from './tarball.mjs'; const configDir = fileURLToPath(new URL('../.config', import.meta.url)); const configPath = process.env.MISSKEY_CONFIG_YML @@ -23,91 +16,19 @@ const configPath = process.env.MISSKEY_CONFIG_YML ? path.resolve(configDir, 'test.yml') : path.resolve(configDir, 'default.yml'); -let locales = buildLocales(); - async function loadConfig() { return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null); } async function copyFrontendFonts() { - await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true }); -} - -async function copyFrontendTablerIcons() { - await fs.cp('./packages/frontend/node_modules/@tabler/icons-webfont/dist', './built/_frontend_dist_/tabler-icons', { dereference: true, recursive: true }); -} - -async function copyFrontendLocales() { - generateDTS(); - - await fs.mkdir('./built/_frontend_dist_/locales', { recursive: true }); - - const v = { '_version_': meta.version }; - - for (const [lang, locale] of Object.entries(locales)) { - await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8'); - } -} - -async function copyBackendViews() { - await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true }); -} - -async function buildBackendScript() { - await fs.mkdir('./packages/backend/built/server/web', { recursive: true }); - - for (const file of [ - './packages/backend/src/server/web/boot.js', - './packages/backend/src/server/web/boot.embed.js', - './packages/backend/src/server/web/bios.js', - './packages/backend/src/server/web/cli.js', - './packages/backend/src/server/web/error.js', - ]) { - let source = await fs.readFile(file, { encoding: 'utf-8' }); - source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales))); - const { code } = await terser.minify(source, { toplevel: true }); - await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code); - } -} - -async function buildBackendStyle() { - await fs.mkdir('./packages/backend/built/server/web', { recursive: true }); - - for (const file of [ - './packages/backend/src/server/web/style.css', - './packages/backend/src/server/web/style.embed.css', - './packages/backend/src/server/web/bios.css', - './packages/backend/src/server/web/cli.css', - './packages/backend/src/server/web/error.css' - ]) { - const source = await fs.readFile(file, { encoding: 'utf-8' }); - const { css } = await postcss([cssnano({ zindex: false })]).process(source, { from: undefined }); - await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, css); - } + await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true }); } async function build() { - await Promise.all([ - copyFrontendFonts(), - copyFrontendTablerIcons(), - copyFrontendLocales(), - copyBackendViews(), - buildBackendScript(), - buildBackendStyle(), + await Promise.all([ + copyFrontendFonts(), loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()), - ]); + ]); } await build(); - -if (process.argv.includes('--watch')) { - const watcher = fs.watch('./locales'); - for await (const event of watcher) { - const filename = event.filename?.replaceAll('\\', '/'); - if (/^[a-z]+-[A-Z]+\.yml/.test(filename)) { - console.log(`update ${filename} ...`) - locales = buildLocales(); - await copyFrontendLocales() - } - } -} diff --git a/scripts/build-pre.js b/scripts/build-pre.mjs similarity index 89% rename from scripts/build-pre.js rename to scripts/build-pre.mjs index a90d53c75d..23c2d08042 100644 --- a/scripts/build-pre.js +++ b/scripts/build-pre.mjs @@ -3,7 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -const fs = require('fs'); +import * as fs from 'node:fs'; + +const __dirname = import.meta.dirname; + const packageJsonPath = __dirname + '/../package.json' function build() { diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json index 57812cbf05..5fb25ae977 100644 --- a/scripts/changelog-checker/package-lock.json +++ b/scripts/changelog-checker/package-lock.json @@ -9,36 +9,22 @@ "version": "1.0.0", "devDependencies": { "@types/mdast": "4.0.4", - "@types/node": "22.13.15", - "@vitest/coverage-v8": "3.1.1", + "@types/node": "24.10.4", + "@vitest/coverage-v8": "4.0.15", "mdast-util-to-string": "4.0.0", "remark": "15.0.1", "remark-parse": "11.0.0", - "typescript": "5.8.2", + "typescript": "5.9.3", "unified": "11.0.5", - "vite": "6.3.1", - "vite-node": "3.1.1", - "vitest": "3.1.1" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "vite": "7.3.0", + "vite-node": "5.2.0", + "vitest": "4.0.15" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -46,9 +32,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -56,13 +42,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -72,14 +58,14 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -96,9 +82,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -113,9 +99,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -130,9 +116,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -147,9 +133,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -164,9 +150,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -181,9 +167,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -198,9 +184,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -215,9 +201,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -232,9 +218,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -249,9 +235,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -266,9 +252,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -283,9 +269,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -300,9 +286,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -317,9 +303,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -334,9 +320,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -351,9 +337,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -368,9 +354,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -385,9 +371,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -402,9 +388,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -419,9 +405,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -436,9 +422,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -452,10 +438,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -470,9 +473,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -487,9 +490,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -504,9 +507,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -520,48 +523,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -571,27 +532,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -599,21 +550,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -625,9 +565,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -639,9 +579,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -653,9 +593,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -667,9 +607,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -681,9 +621,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -695,9 +635,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -709,9 +649,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -723,9 +663,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -737,9 +677,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -750,10 +690,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -764,10 +704,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -779,9 +719,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -793,9 +733,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -807,9 +747,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -821,9 +761,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -835,9 +775,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -848,10 +788,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -863,9 +817,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -876,10 +830,10 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -890,6 +844,38 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -899,10 +885,17 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -923,13 +916,14 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.13.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.15.tgz", - "integrity": "sha512-imAbQEEbVni6i6h6Bd5xkCRwLqFc8hihCsi2GbtDoAtUcAFQ6Zs4pFXTZUUbroTkXdImczWM9AI8eZUuybXE3w==", + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/unist": { @@ -939,31 +933,30 @@ "dev": true }, "node_modules/@vitest/coverage-v8": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.1.tgz", - "integrity": "sha512-MgV6D2dhpD6Hp/uroUoAIvFqA8AuvXEFBC2eepG3WFc1pxTfdk1LEqqkWoWhjz+rytoqrnUUCdf6Lzco3iHkLQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.15.tgz", + "integrity": "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "debug": "^4.4.0", + "@vitest/utils": "4.0.15", + "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.8.1", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.1.1", - "vitest": "3.1.1" + "@vitest/browser": "4.0.15", + "vitest": "4.0.15" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -972,38 +965,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", - "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.15.tgz", + "integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.1", - "@vitest/utils": "3.1.1", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", - "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.15.tgz", + "integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.1", + "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -1015,26 +1010,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", - "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", - "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.15.tgz", + "integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.1", + "@vitest/utils": "4.0.15", "pathe": "^2.0.3" }, "funding": { @@ -1042,14 +1037,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", - "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.15.tgz", + "integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.1", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.15", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -1057,59 +1052,29 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", - "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.15.tgz", + "integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", - "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.1", - "loupe": "^3.1.3", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.15", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1120,6 +1085,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1130,23 +1107,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1157,20 +1117,13 @@ } }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/character-entities": { @@ -1183,54 +1136,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1258,16 +1167,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1290,31 +1189,17 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1325,31 +1210,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/estree-walker": { @@ -1363,9 +1249,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1379,11 +1265,14 @@ "dev": true }, "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -1393,23 +1282,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1425,27 +1297,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1461,16 +1312,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -1483,12 +1324,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -1528,9 +1363,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1541,21 +1376,12 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", @@ -1567,13 +1393,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1587,25 +1406,25 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -2136,32 +1955,6 @@ } ] }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2170,9 +1963,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -2188,45 +1981,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" }, "node_modules/pathe": { "version": "2.0.3", @@ -2235,16 +1999,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2253,11 +2007,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2266,9 +2021,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -2286,7 +2041,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2342,13 +2097,13 @@ } }, "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -2358,26 +2113,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, @@ -2396,44 +2153,12 @@ "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", @@ -2448,119 +2173,16 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/std-env": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", - "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2573,21 +2195,6 @@ "node": ">=8" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2596,21 +2203,24 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -2619,30 +2229,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", - "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -2660,9 +2250,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2674,9 +2264,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "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" }, @@ -2785,24 +2375,24 @@ } }, "node_modules/vite": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.1.tgz", - "integrity": "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.3", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.12" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2811,14 +2401,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -2860,71 +2450,74 @@ } }, "node_modules/vite-node": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", - "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-5.2.0.tgz", + "integrity": "sha512-7UT39YxUukIA97zWPXUGb0SGSiLexEGlavMwU3HDE6+d/HJhKLjLqu4eX2qv6SQiocdhKLRcusroDwXHQ6CnRQ==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", + "es-module-lexer": "^1.7.0", + "obug": "^2.0.0", "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^7.2.2" }, "bin": { - "vite-node": "vite-node.mjs" + "vite-node": "dist/cli.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://opencollective.com/antfu" } }, "node_modules/vitest": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", - "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz", + "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@vitest/expect": "3.1.1", - "@vitest/mocker": "3.1.1", - "@vitest/pretty-format": "^3.1.1", - "@vitest/runner": "3.1.1", - "@vitest/snapshot": "3.1.1", - "@vitest/spy": "3.1.1", - "@vitest/utils": "3.1.1", - "chai": "^5.2.0", - "debug": "^4.4.0", - "expect-type": "^1.2.0", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.15", + "@vitest/mocker": "4.0.15", + "@vitest/pretty-format": "4.0.15", + "@vitest/runner": "4.0.15", + "@vitest/snapshot": "4.0.15", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "std-env": "^3.8.1", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinypool": "^1.0.2", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.1", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.1", - "@vitest/ui": "3.1.1", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", + "@vitest/ui": "4.0.15", "happy-dom": "*", "jsdom": "*" }, @@ -2932,13 +2525,19 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -2952,21 +2551,6 @@ } } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -2984,104 +2568,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json index 258d670a89..bf68efb053 100644 --- a/scripts/changelog-checker/package.json +++ b/scripts/changelog-checker/package.json @@ -10,15 +10,15 @@ }, "devDependencies": { "@types/mdast": "4.0.4", - "@types/node": "22.13.15", - "@vitest/coverage-v8": "3.1.1", + "@types/node": "24.10.4", + "@vitest/coverage-v8": "4.0.15", "mdast-util-to-string": "4.0.0", "remark": "15.0.1", "remark-parse": "11.0.0", - "typescript": "5.8.2", + "typescript": "5.9.3", "unified": "11.0.5", - "vite": "6.3.1", - "vite-node": "3.1.1", - "vitest": "3.1.1" + "vite": "7.3.0", + "vite-node": "5.2.0", + "vitest": "4.0.15" } } diff --git a/scripts/clean-all.js b/scripts/clean-all.mjs similarity index 81% rename from scripts/clean-all.js rename to scripts/clean-all.mjs index 8188a01db4..711f05e74f 100644 --- a/scripts/clean-all.js +++ b/scripts/clean-all.mjs @@ -3,16 +3,21 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -const { execSync } = require('child_process'); -const fs = require('fs'); +import { execSync } from 'node:child_process'; +import * as fs from 'node:fs'; + +const __dirname = import.meta.dirname; (async () => { fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/backend/src-js', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/backend/node_modules', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-shared/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-builder/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend/node_modules', { recursive: true, force: true }); @@ -22,6 +27,9 @@ const fs = require('fs'); fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/i18n/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-js/node_modules', { recursive: true, force: true }); diff --git a/scripts/clean.js b/scripts/clean.mjs similarity index 75% rename from scripts/clean.js rename to scripts/clean.mjs index 959c6c869e..36d9199ca0 100644 --- a/scripts/clean.js +++ b/scripts/clean.mjs @@ -3,13 +3,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -const fs = require('fs'); +import * as fs from 'node:fs'; + +const __dirname = import.meta.dirname; (async () => { fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/backend/src-js', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/icons-subsetter/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true }); diff --git a/scripts/dependency-patches/re2.patch b/scripts/dependency-patches/re2.patch deleted file mode 100644 index 3e7ec9f56f..0000000000 --- a/scripts/dependency-patches/re2.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/package.json b/package.json -index a56ab59ef647288ee6028abd2b1780eaa92ebc9d..ec2c43e63f3134b6d54d616b2ef715447f873bbe 100644 ---- a/package.json -+++ b/package.json -@@ -28,7 +28,7 @@ - "test": "node tests/tests.js", - "ts-test": "tsc", - "save-to-github": "save-to-github-cache --artifact build/Release/re2.node", -- "install": "install-from-cache --artifact build/Release/re2.node --host-var RE2_DOWNLOAD_MIRROR --skip-path-var RE2_DOWNLOAD_SKIP_PATH --skip-ver-var RE2_DOWNLOAD_SKIP_VER || node-gyp -j max rebuild", -+ "install": "npm_package_github=https://github.com/uhop/node-re2 npm_package_scripts_verify_build=true install-from-cache --artifact build/Release/re2.node --host-var RE2_DOWNLOAD_MIRROR --skip-path-var RE2_DOWNLOAD_SKIP_PATH --skip-ver-var RE2_DOWNLOAD_SKIP_VER || node-gyp -j max rebuild", - "verify-build": "node scripts/verify-build.js", - "build:dev": "node-gyp -j max build --debug", - "build": "node-gyp -j max build", diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 2b612438ad..eb9575f56f 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -16,6 +16,13 @@ await execa('pnpm', ['clean'], { stderr: process.stderr, }); +// アセットのビルドで依存しているので一番最初に必要 +await execa('pnpm', ['--filter', 'i18n', 'build'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, +}); + await Promise.all([ execa('pnpm', ['build-pre'], { cwd: _dirname + '/../', @@ -32,6 +39,17 @@ await Promise.all([ stdout: process.stdout, stderr: process.stderr, }), + // icons-subsetterは開発段階では使用されないが、型エラーを抑制するためにはじめの一度だけビルドする + execa('pnpm', ['--filter', 'icons-subsetter', 'build'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, + }), + execa('pnpm', ['--filter', 'misskey-js', 'build'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, + }), ]); await execa('pnpm', ['--filter', 'misskey-mahjong', 'build:tsc'], { @@ -88,6 +106,12 @@ execa('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], { stderr: process.stderr, }); +execa('pnpm', ['--filter', 'i18n', 'watch', '--no-clean'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, +}); + execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], { cwd: _dirname + '/../', stdout: process.stdout, diff --git a/scripts/tarball.mjs b/scripts/tarball.mjs index fddbe3b04a..dc6ee07773 100644 --- a/scripts/tarball.mjs +++ b/scripts/tarball.mjs @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { createWriteStream } from 'node:fs'; +import { createWriteStream, promises as fsp } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import glob from 'fast-glob'; import walk from 'ignore-walk'; import { Pack } from 'tar/pack'; import meta from '../package.json' with { type: "json" }; @@ -20,12 +19,12 @@ const ignore = [ // Exclude files you don't want to include in the tarball here ]; -export default async function build() { +export async function buildTarball() { const mkdirPromise = mkdir(resolve(cwd, 'built', 'tarball'), { recursive: true }); const pack = new Pack({ cwd, gzip: true }); const patterns = await walk({ path: cwd, ignoreFiles: ['.gitignore'] }); - for await (const entry of glob.stream(patterns, { cwd, ignore, dot: true })) { + for await (const entry of fsp.glob(patterns, { cwd, ignore, dot: true })) { pack.add(entry); }