diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 3f8e5734ce87..ad9ae4fd9adf 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -114,9 +114,27 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌───────────────────────────┐ -#───┘ MeiliSearch configuration └───────────────────────────── - +# ┌───────────────────────────────┐ +#───┘ Fulltext search configuration └───────────────────────────── + +# These are the setting items for the full-text search provider. +fulltextSearch: + # You can select the ID generation method. + # - sqlLike (default) + # Use SQL-like search. + # This is a standard feature of PostgreSQL, so no special extensions are required. + # - sqlPgroonga + # Use pgroonga. + # You need to install pgroonga and configure it as a PostgreSQL extension. + # In addition to the above, you need to create a pgroonga index on the text column of the note table. + # see: https://pgroonga.github.io/tutorial/ + # - meilisearch + # Use Meilisearch. + # You need to install Meilisearch and configure. + provider: sqlLike + +# For Meilisearch settings. +# If you select "meilisearch" for "fulltextSearch.provider", it must be set. # You can set scope to local (default value) or global # (include notes from remote). @@ -219,3 +237,13 @@ signToActivityPubGet: true # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.config/example.yml b/.config/example.yml index 60a6a0aa71b7..349c2e9730e6 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -196,9 +196,27 @@ redis: # # You can specify more ioredis options... # #username: example-username -# ┌───────────────────────────┐ -#───┘ MeiliSearch configuration └───────────────────────────── - +# ┌───────────────────────────────┐ +#───┘ Fulltext search configuration └───────────────────────────── + +# These are the setting items for the full-text search provider. +fulltextSearch: + # You can select the ID generation method. + # - sqlLike (default) + # Use SQL-like search. + # This is a standard feature of PostgreSQL, so no special extensions are required. + # - sqlPgroonga + # Use pgroonga. + # You need to install pgroonga and configure it as a PostgreSQL extension. + # In addition to the above, you need to create a pgroonga index on the text column of the note table. + # see: https://pgroonga.github.io/tutorial/ + # - meilisearch + # Use Meilisearch. + # You need to install Meilisearch and configure. + provider: sqlLike + +# For Meilisearch settings. +# If you select "meilisearch" for "fulltextSearch.provider", it must be set. # You can set scope to local (default value) or global # (include notes from remote). @@ -321,3 +339,13 @@ signToActivityPubGet: true # PID File of master process #pidFile: /tmp/misskey.pid + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml index 315e712c30e4..077855b5bf35 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: 2024.x.x + * Misskey: 2025.x.x value: | * Model and OS of the device(s): * Browser: @@ -74,7 +74,7 @@ body: Examples: * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment - * Misskey: 2024.x.x + * Misskey: 2025.x.x * Node: 20.x.x * PostgreSQL: 15.x.x * Redis: 7.x.x diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 8380a3bb238a..e21738c4f4a8 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -21,7 +21,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.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 44cc1a04f2c1..fdca621cfc92 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -14,7 +14,7 @@ jobs: - name: Checkout head uses: actions/checkout@v4.1.1 - name: Setup Node.js - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.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 f26c9a4d45b8..bb155419417e 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -29,7 +29,7 @@ jobs: - name: setup node id: setup-node - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: pnpm diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 972619ec603f..46c726b986c9 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -33,7 +33,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 90eb268ddab8..9785bb5744dc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -68,14 +68,14 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.1.0 + uses: actions/cache@v4.2.0 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} @@ -98,7 +98,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index 6bc8860a11eb..2eb4ca3ad93c 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 6258fa693a45..8ca2ed9efb8c 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -26,7 +26,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/release-with-ready.yml b/.github/workflows/release-with-ready.yml deleted file mode 100644 index 585375c20e73..000000000000 --- a/.github/workflows/release-with-ready.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Release Manager: release RC when ready for review" - -on: - pull_request: - types: [ready_for_review] - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -permissions: - contents: write - issues: write - pull-requests: write - -jobs: - check: - runs-on: ubuntu-latest - outputs: - head: ${{ steps.get_pr.outputs.head }} - base: ${{ steps.get_pr.outputs.base }} - steps: - - uses: actions/checkout@v4 - # PR情報を取得 - - name: Get PR - run: | - pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName,baseRefName) - echo "head=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT - echo "base=$(echo $pr_json | jq -r '.baseRefName')" >> $GITHUB_OUTPUT - id: get_pr - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - release: - uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2 - needs: check - if: needs.check.outputs.head == github.event.repository.default_branch && needs.check.outputs.base == vars.STABLE_BRANCH - with: - pr_number: ${{ github.event.pull_request.number }} - user: 'github-actions[bot]' - package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} - use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} - indent: ${{ vars.INDENT }} - draft_prerelease_channel: alpha - ready_start_prerelease_channel: beta - reset_number_on_channel_change: true - secrets: - RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} - RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index c02f38ee0b61..dfba46a8c800 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -15,6 +15,8 @@ on: jobs: build: + # chromatic is not likely to be available for fork repositories, so we disable for fork repositories. + if: github.repository == 'misskey-dev/misskey' runs-on: ubuntu-latest env: @@ -41,7 +43,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js 20.x - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index fc614dcf8548..debfe24819ca 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -10,14 +10,17 @@ on: # for permissions - packages/misskey-js/** - .github/workflows/test-backend.yml + - .github/misskey/test.yml pull_request: paths: - packages/backend/** # for permissions - packages/misskey-js/** - .github/workflows/test-backend.yml + - .github/misskey/test.yml jobs: unit: + name: Unit tests (backend) runs-on: ubuntu-latest strategy: @@ -44,9 +47,22 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Install FFmpeg - uses: FedericoCarboni/setup-ffmpeg@v3 + run: | + for i in {1..3}; do + echo "Attempt $i: Installing FFmpeg..." + curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \ + tar -xf ffmpeg.tar.xz && \ + mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \ + mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \ + rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \ + break || sleep 10 + if [ $i -eq 3 ]; then + echo "Failed to install FFmpeg after 3 attempts" + exit 1 + fi + done - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -67,6 +83,7 @@ jobs: files: ./packages/backend/coverage/coverage-final.json e2e: + name: E2E tests (backend) runs-on: ubuntu-latest strategy: @@ -93,7 +110,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml index e89cdcb0916e..c4546a0590c7 100644 --- a/.github/workflows/test-federation.yml +++ b/.github/workflows/test-federation.yml @@ -17,6 +17,7 @@ on: jobs: test: + name: Federation test runs-on: ubuntu-latest strategy: matrix: @@ -28,9 +29,22 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Install FFmpeg - uses: FedericoCarboni/setup-ffmpeg@v3 + run: | + for i in {1..3}; do + echo "Attempt $i: Installing FFmpeg..." + curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \ + tar -xf ffmpeg.tar.xz && \ + mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \ + mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \ + rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \ + break || sleep 10 + if [ $i -eq 3 ]; then + echo "Failed to install FFmpeg after 3 attempts" + exit 1 + fi + done - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 6128abb5025e..51e0b0e8b8a4 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -12,6 +12,7 @@ on: # for e2e - packages/backend/** - .github/workflows/test-frontend.yml + - .github/misskey/test.yml pull_request: paths: - packages/frontend/** @@ -20,8 +21,10 @@ on: # for e2e - packages/backend/** - .github/workflows/test-frontend.yml + - .github/misskey/test.yml jobs: vitest: + name: Unit tests (frontend) runs-on: ubuntu-latest strategy: @@ -35,7 +38,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -56,6 +59,7 @@ jobs: files: ./packages/frontend/coverage/coverage-final.json e2e: + name: E2E tests (frontend) runs-on: ubuntu-latest strategy: @@ -90,7 +94,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index c7bb0753a8f1..c72a2470a474 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -16,6 +16,7 @@ on: - .github/workflows/test-misskey-js.yml jobs: test: + name: Unit tests (misskey.js) runs-on: ubuntu-latest @@ -31,7 +32,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 11a95ca82f1e..4a55f4803c88 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -12,6 +12,7 @@ env: jobs: production: + name: Production build runs-on: ubuntu-latest strategy: @@ -25,7 +26,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index 835b2a9a24c0..0d254898f8a2 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -1,4 +1,4 @@ -name: Test (backend) +name: api.json validation on: push: @@ -27,7 +27,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa756c0f050..3a4aa3932bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,82 @@ +## 2025.1.0 + +### Note +- [重要] ノート検索プロバイダの追加に伴い、configファイル(default.ymlなど)の構成が少し変わります. + - 新しい設定項目"fulltextSearch.provider"が追加されました. sqlLike, sqlPgroonga, meilisearchのいずれかを設定出来ます. + - すでにMeilisearchをお使いの場合、 **"fulltextSearch.provider"を"meilisearch"に設定する必要** があります. + - 詳細は #14730 および `.config/example.yml` または `.config/docker_example.yml`の'Fulltext search configuration'をご参照願います. +- 【開発者向け】従来の開発モードでHMRが機能しない問題が修正されたため、バックエンド・フロントエンド分離型の開発モードが削除されました。開発環境においてconfigの変更が必要となる可能性があります。 + +### General +- Feat: カスタム絵文字管理画面をリニューアル #10996 + * β版として公開のため、旧画面も引き続き利用可能です + +### Client +- Enhance: PC画面でチャンネルが複数列で表示されるように + (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) +- Enhance: 照会に失敗した場合、その理由を表示するように +- Enhance: ワードミュートで検知されたワードを表示できるように +- Enhance: リモートのノートのリンクをコピーできるように +- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正 +- Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加 +- Enhance: ノートの添付ファイルを一覧で遡れる「ファイル」タブを追加 + (Based on https://github.com/Otaku-Social/maniakey/pull/14) +- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に +- Enhance: クエリパラメータでuiを一時的に変更できるように #15240 +- Enhance: リモート絵文字のインポート時に詳細を確認できるように #15336 +- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 +- Fix: サーバー情報メニューに区切り線が不足していたのを修正 +- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 +- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) +- Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正 +- Fix: プラグイン `register_note_view_interruptor` でノートのサーバー情報の書き換えができない問題を修正 +- Fix: Botプロテクションの設定変更時は実際に検証を通過しないと保存できないように( #15137 ) +- Fix: ノート検索が使用できない場合でもチャンネルのノート検索欄がでていた問題を修正 +- Fix: `Ui:C:select`で値の変更が画面に反映されない問題を修正 +- Fix: MiAuth認可画面で、認可処理に失敗した場合でもコールバックURLに遷移してしまう問題を修正 + (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) +- Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 +- Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 +- Fix: MacOSでChrome系ブラウザを使用している場合に、Misskeyを閉じた際に他のタブのオーディオ機能と干渉する問題を修正 +- Fix: 言語データのキャッシュ状況によっては、埋め込みウィジェットが正しく起動しない問題を修正 +- Fix: 「削除して編集」でノートの引用を解除出来なかった問題を修正( #14476 ) +- Fix: RSSウィジェットが正しく表示されない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/857) +- Fix: ワードミュートの保存失敗時にAPIエラーが握りつぶされる事があるのを修正 +- Fix: アンケートでリモートの絵文字が正しく描画できない問題の修正 + (Cherry-picked from https://github.com/yojo-art/cherrypick/pull/153) +- Fix: 非ログイン時のサーバー概要画面のメニューボタンが押せないことがあるのを修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/656) +- Fix: URLにはじめから`#pswp`が含まれている場合に画像ビューワーがブラウザの戻るボタンで閉じられない問題を修正 +- Fix: ロール作成画面で設定できるアイコンデコレーションの最大取付個数を16に制限 +- Fix: Firefox Nightlyなどでアイコンが読み込めない問題を修正 + +### Server +- Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように +- Enhance: ノート検索の選択肢としてpgroongaに対応 ( #14730 ) +- Enhance: チャート更新時にDBに同時接続しないように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) +- Enhance: config(default.yml)からSQLログ全文を出力するか否かを設定可能に ( #15266 ) +- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) +- Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) +- Fix: ノートの閲覧にログイン必須にしてもFeedでノートが表示されてしまう問題を修正 +- Fix: 絵文字の連合でライセンス欄を相互にやり取りするように ( #10859, #14109 ) +- Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) +- Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) +- Fix: URLとURIが異なるエンティティの照会に失敗する問題を修正( #15039 ) +- Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) +- Fix: `/api/pages/update`にて`name`を指定せずにリクエストするとエラーが発生する問題を修正 +- Fix: AIセンシティブ判定が arm64 環境で動作しない問題を修正 +- Fix: 非Misskey系のソフトウェアからHTML``タグを含むノートを受信した場合、MFMの読み仮名(ルビ)文法に変換して表示 +- Fix: 連合OFFで投稿されたノートに対する冗長な処理を抑止 ( #15018 ) +- Fix: `/api.json`のレスポンスが2回目のリクエスト以降おかしくなる問題を修正 + +### Misskey.js +- Feat: allow setting `binaryType` of WebSocket connection + ## 2024.11.0 ### Note @@ -37,7 +116,7 @@ - Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正 - Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used - Fix: リンク切れを修正 -= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正 +- Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正 (Cherry-picked from https://github.com/taiyme/misskey/pull/305) - Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 - Fix: 画面幅が狭い環境でデザインが崩れる問題を修正 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c777259d28d..0c63f69cf133 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,25 +197,10 @@ pnpm dev command. - Server-side source files and automatically builds them if they are modified. Automatically start the server process(es). -- Vite HMR (just the `vite` command) is available. The behavior may be different from production. - Service Worker is watched by esbuild. -- The front end can be viewed by accessing `http://localhost:5173`. -- The backend listens on the port configured with `port` in .config/default.yml. -If you have not changed it from the default, it will be "http://localhost:3000". -If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts. - -### `MK_DEV_PREFER=backend pnpm dev` -pnpm dev has another mode with `MK_DEV_PREFER=backend`. - -``` -MK_DEV_PREFER=backend pnpm dev -``` - -- This mode is closer to the production environment than the default mode. -- Vite runs behind the backend (the backend will proxy Vite at /vite). +- Vite HMR (just the `vite` command) is available. The behavior may be different from production. +- Vite runs behind the backend (the backend will proxy Vite at /vite and /embed_vite except for websocket used for HMR). - You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml). -- To change the port of Vite, specify with `VITE_PORT` environment variable. -- HMR may not work in some environments such as Windows. ## Testing You can run non-backend tests by executing following commands: @@ -491,6 +476,11 @@ describe('test', () => { コード上でMisskeyのドメイン固有の概念には`Mi`をprefixすることで、他のドメインの同様の概念と区別できるほか、名前の衝突を防ぐ。 ただし、文脈上Misskeyのものを指すことが明らかであり、名前の衝突の恐れがない場合は、一時的なローカル変数に限って`Mi`を省略してもよい。 +### Misskey.jsの型生成 +```bash +pnpm build-misskey-js-with-types +``` + ### How to resolve conflictions occurred at pnpm-lock.yaml? Just execute `pnpm` to fix it. diff --git a/COPYING b/COPYING index 6a5f3ca1d598..7635bfc913a7 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2024 syuilo and contributors +Copyright © 2014-2025 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 ee765abe7cb2..13f690946285 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=22.11.0-bullseye +ARG NODE_VERSION=22.11.0-bookworm # build assets & compile TypeScript diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 2f1b391b5359..1f885c66a2da 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1584,3 +1584,6 @@ _reversi: _offlineScreen: title: "غير متصل - يتعذر الاتصال بالخادم" header: "يتعذر الاتصال بالخادم" +_remoteLookupErrors: + _noSuchObject: + title: "غير موجود" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6cd577b4a962..9c8566c5b735 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1348,3 +1348,6 @@ _moderationLogTypes: resetPassword: "পাসওয়ার্ড রিসেট করুন" _reversi: total: "মোট" +_remoteLookupErrors: + _noSuchObject: + title: "পাওয়া যায়নি" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 1aca3390e6fc..9954b2a747f1 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -5,6 +5,7 @@ introMisskey: "Benvingut! Misskey és un servei de microblogging descentralitzat poweredByMisskeyDescription: "{name} És un dels serveis (anomenats instàncies de Misskey) que utilitzen la plataforma de codi obert Misskey." monthAndDay: "{day}/{month}" search: "Cercar" +reset: "Reiniciar" notifications: "Notificacions" username: "Nom d'usuari" password: "Contrasenya" @@ -43,11 +44,12 @@ favorites: "Favorits" unfavorite: "Eliminar dels preferits" favorited: "Afegit als preferits." alreadyFavorited: "Ja s'ha afegit als preferits." -cantFavorite: "No s'ha pogut afegir als preferits." -pin: "Fixar al perfil" +cantFavorite: "No es pot afegir als preferits." +pin: "Fixa al perfil" unpin: "Para de fixar del perfil" -copyContent: "Copiar el contingut" -copyLink: "Copiar l'enllaç" +copyContent: "Copia el contingut" +copyLink: "Copia l'enllaç" +copyRemoteLink: "Copiar l'enllaç remot" copyLinkRenote: "Copiar l'enllaç de la renota" delete: "Elimina" deleteAndEdit: "Eliminar i editar" @@ -103,9 +105,9 @@ enterListName: "Introdueix un nom per a la llista" privacy: "Privadesa" makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació" defaultNoteVisibility: "Visibilitat per defecte" -follow: "Seguint" +follow: "Segueix" followRequest: "Enviar sol·licitud de seguiment" -followRequests: "Sol·licituds de seguiment" +followRequests: "Peticions de seguiment" unfollow: "Deixar de seguir" followRequestPending: "Sol·licituds de seguiment pendents" enterEmoji: "Introduir un emoji" @@ -195,7 +197,7 @@ setWallpaper: "Defineix el fons de pantalla" removeWallpaper: "Elimina el fons de pantalla" searchWith: "Cerca: {q}" youHaveNoLists: "No tens cap llista" -followConfirm: "Estàs segur que vols deixar de seguir {name}?" +followConfirm: "Segur que vols seguir a {name}?" proxyAccount: "Compte de proxy" proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà." host: "Amfitrió" @@ -222,7 +224,7 @@ version: "Versió" metadata: "Metadades" withNFiles: "{n} fitxer(s)" monitor: "Monitor" -jobQueue: "Cua de tasques" +jobQueue: "Cua de feines" cpuAndMemory: "CPU i memòria" network: "Xarxa" disk: "Disc" @@ -325,7 +327,7 @@ dark: "Fosc" lightThemes: "Temes clars" darkThemes: "Temes foscos" syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu" -drive: "Unitat" +drive: "Disc" fileName: "Nom del Fitxer" selectFile: "Selecciona un fitxer" selectFiles: "Selecciona fitxers" @@ -340,11 +342,11 @@ deleteFolder: "Elimina la carpeta" folder: "Carpeta " addFile: "Afegeix un fitxer" showFile: "Mostrar fitxer" -emptyDrive: "La teva unitat és buida" +emptyDrive: "El teu Disc és buit" emptyFolder: "La carpeta està buida" unableToDelete: "No es pot eliminar" inputNewFileName: "Introduïu el nom de fitxer nou" -inputNewDescription: "Inserta una nova llegenda" +inputNewDescription: "Escriu el peu de foto." inputNewFolderName: "Introduïu el nom de la carpeta nova" circularReferenceFolder: "La carpeta destinatària és una subcarpeta de la carpeta a la qual la desitges moure" hasChildFilesOrFolders: "No és possible esborrar aquesta carpeta ja que no és buida" @@ -537,7 +539,7 @@ mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única limitTo: "Limita a {x}" noFollowRequests: "No tens sol·licituds de seguiment" openImageInNewTab: "Obre imatges a una nova pestanya" -dashboard: "Panell de control" +dashboard: "Taulell de control" local: "Local" remote: "Remot" total: "Total" @@ -573,7 +575,7 @@ serverLogs: "Registres del servidor" deleteAll: "Elimina-ho tot" showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps" showFixedPostFormInChannel: "Mostrar el formulari d'escriptura al principi de la línia de temps (Canals)" -withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous seguits a la línia de temps per defecte." +withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous que segueixes a la línia de temps per defecte." newNoteRecived: "Hi ha publicacions noves" sounds: "Sons" sound: "So" @@ -614,7 +616,7 @@ unsetUserBanner: "Desactiva el bàner " unsetUserBannerConfirm: "Segur que vols desactivar el bàner?" deleteAllFiles: "Esborra tots els arxius" deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?" -removeAllFollowing: "Deixa de seguir tots els usuaris seguits" +removeAllFollowing: "Deixa de seguir tots els usuaris que segueixes" removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix." userSuspended: "Aquest usuari ha sigut suspès" userSilenced: "Aquest usuari està sent silenciat" @@ -645,7 +647,7 @@ expandTweet: "Expandir post" themeEditor: "Editor de temes" description: "Descripció" describeFile: "Afegir subtitulació" -enterFileDescription: "Afegeix un títol" +enterFileDescription: "Escriu un peu de foto" author: "Autor" leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?" manage: "Administració" @@ -684,11 +686,15 @@ smtpSecure: "Fes servir SSL/TLS per connexions SMTP" smtpSecureInfo: "Desactiva això quan facis servir connexions STARTTLS" testEmail: "Prova l'enviament de correu " wordMute: "Silenciar paraules " +wordMuteDescription: "Minimitza les notes que contenen la paraula o frase especificada. Les notes minimitzades poden visualitzar-se fent clic sobre elles." hardWordMute: "Silenciar paraules fortes" +showMutedWord: "Mostrar paraules silenciades" +hardWordMuteDescription: "Oculta les notes que contenen la paraula o frase especificada. A diferència de Silenciar paraula, la nota quedarà completament oculta a la vista." regexpError: "Error de l'expressió regular " regexpErrorDescription: "S'ha produït un error a l'expressió regular a la línia {line} de les paraules silenciades {tab}:" instanceMute: "Silenciar servidor" userSaysSomething: "{name} n'ha dit alguna cosa" +userSaysSomethingAbout: "{name} està parlant sobre \"{word}\"" makeActive: "Activar" display: "Veure" copy: "Copiar" @@ -729,7 +735,7 @@ instanceTicker: "Informació de notes de la instància " waitingFor: "Esperant {x}" random: "Aleatori " system: "Sistema" -switchUi: "Canviar interfície d'usuari " +switchUi: "Canviar la interfície" desktop: "Escriptori" clip: "Retalls" createNew: "Crear" @@ -747,7 +753,7 @@ repliesCount: "Nombre de respostes" renotesCount: "Impulsos fets" repliedCount: "Nombre de respostes rebudes" renotedCount: "Impulsos rebuts" -followingCount: "Nombre de comptes seguits" +followingCount: "Nombre de comptes que segueixes" followersCount: "Nombre de seguidors" sentReactionsCount: "Nombre de reaccions enviades" receivedReactionsCount: "Nombre de reaccions rebudes" @@ -779,7 +785,7 @@ 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: "Mostra una separació entre els articles a la línia de temps" +showGapBetweenNotesInTimeline: "Notes separades a la línia de temps" duplicate: "Duplicat" left: "Esquerra" center: "Centre" @@ -910,7 +916,7 @@ off: "Desactivar" emailRequiredForSignup: "Demanar correu electrònic per registrar-se " unread: "Sense llegir" filter: "Filtrar" -controlPanel: "Panel de control" +controlPanel: "Taulell de control" manageAccounts: "Gestionar comptes" makeReactionsPublic: "Reaccions públiques " makeReactionsPublicDescription: "Això fa que totes les teves reaccions siguin visibles públicament " @@ -929,7 +935,7 @@ useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calai welcomeBackWithName: "Benvingut de nou, {name}" clickToFinishEmailVerification: "Si us plau, fes clic a [{ok}] per completar la verificació per correu electrònic " overridedDeviceKind: "Tipus de dispositiu" -smartphone: "Telèfon intel·ligent" +smartphone: "Mòbil " tablet: "Tauleta" auto: "Automàtic " themeColor: "Color del tema" @@ -1010,7 +1016,7 @@ sendPushNotificationReadMessageCaption: "Això pot fer que el teu dispositiu con windowMaximize: "Maximitzar " windowMinimize: "Minimitzar" windowRestore: "Restaurar" -caption: "Llegenda" +caption: "Peu de foto" loggedInAsBot: "Identificat com a bot" tools: "Eines" cannotLoad: "No es pot carregar" @@ -1133,7 +1139,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d thisChannelArchived: "Aquest Canal ha sigut arxivat." displayOfNote: "Mostrar notes" initialAccountSetting: "Configuració del perfil" -youFollowing: "Seguit" +youFollowing: "Seguint" preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)" preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." options: "Opcions" @@ -1199,7 +1205,7 @@ showRenotes: "Mostrar impulsos" edited: "Editat" notificationRecieveConfig: "Paràmetres de notificacions" mutualFollow: "Seguidor mutu" -followingOrFollower: "Seguit o seguidor" +followingOrFollower: "Seguint o seguidor" fileAttachedOnly: "Només notes amb adjunts" showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la línia de temps" hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la línia de temps" @@ -1301,6 +1307,8 @@ lockdown: "Bloquejat" pleaseSelectAccount: "Seleccionar un compte" 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." _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ó." @@ -1473,7 +1481,7 @@ _accountMigration: startMigration: "Migrar" migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més." movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer." - postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte." + postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de terminar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte." movedTo: "Nou compte:" _achievements: earnedAt: "Desbloquejat el" @@ -1832,7 +1840,7 @@ _emailUnavailable: smtp: "Aquest servidor de correu electrònic no respon" banned: "No pots registrar-te amb aquesta adreça de correu electrònic " _ffVisibility: - public: "Publicar" + public: "Públic " followers: "Visible només per a seguidors " private: "Privat" _signup: @@ -1866,7 +1874,7 @@ _gallery: unlike: "Ja no m'agrada" _email: _follow: - title: "t'ha seguit" + title: "Tens un nou seguidor" _receiveFollowRequest: title: "Has rebut una sol·licitud de seguiment" _plugin: @@ -1900,7 +1908,7 @@ _registry: domain: "Domini" createKey: "Crear una clau" _aboutMisskey: - about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014" + about: "Misskey és un programa de codi obert desenvolupat des del 2014 per syuilo" contributors: "Col·laboradors principals" allContributors: "Tots els col·laboradors " source: "Codi font" @@ -2219,7 +2227,7 @@ _widgets: slideshow: "Presentació" button: "Botó " onlineUsers: "Usuaris actius" - jobQueue: "Cua de tasques" + jobQueue: "Cua de feines" serverMetric: "Mètriques del servidor" aiscript: "Consola AiScript" aiscriptApp: "Aplicació AiScript" @@ -2299,7 +2307,7 @@ _exportOrImport: allNotes: "Totes les publicacions" favoritedNotes: "Notes preferides" clips: "Retalls" - followingList: "Seguint" + followingList: "Seguint " muteList: "Silencia" blockingList: "Bloqueja" userLists: "Llistes" @@ -2438,7 +2446,7 @@ _notification: _types: all: "Tots" note: "Notes noves" - follow: "Seguint" + follow: "Segueix-me" mention: "Menció" reply: "Respostes" renote: "Renotar" @@ -2454,7 +2462,7 @@ _notification: test: "Prova la notificació" app: "Notificacions d'aplicacions" _actions: - followBack: "t'ha seguit també" + followBack: "També et segueix" reply: "Respondre" renote: "Renotar" _deck: @@ -2721,6 +2729,66 @@ _contextMenu: app: "Aplicació " appWithShift: "Aplicació amb la tecla shift" native: "Interfície del navegador" +_gridComponent: + _error: + requiredValue: "Aquest camp és obligatori" + columnTypeNotSupport: "La validació d'expressions regulars només s'admet per columnes de tipus text." + patternNotMatch: "Aquest valor no coincideix amb {pattern}" + notUnique: "Aquest valor ha de ser únic " +_roleSelectDialog: + notSelected: "No seleccionat" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copiar línies seleccionades " + copySelectionRanges: "Copiar selecció " + deleteSelectionRows: "Esborrar línies seleccionades" + deleteSelectionRanges: "Esborrar files de la selecció " + searchSettings: "Configuració del cercador" + searchSettingCaption: "Defineix criteris de cerca detallats." + searchLimit: "Nombre de pantalles" + sortOrder: "Ordenar" + registrationLogs: "Registres d'inscripcions " + registrationLogsCaption: "Quan s'actualitzin o s'esborrin emojis es mostrarà un registre. Desapareixeran quan s'actualitzin, s'esborrin, visitis una nova pàgina o la recarreguis." + alertEmojisRegisterFailedDescription: "No s'ha pogut actualitzar o esborrar l'emoji. Si us plau, dona una ullada al registre per més detalls." + _logs: + showSuccessLogSwitch: "Mostrar el registre d'èxit " + failureLogNothing: "No hi ha registres de fallades." + logNothing: "No hi ha registres." + _remote: + selectionRowDetail: "Detall de la línia seleccionada" + importSelectionRows: "Importar les files seleccionades" + importSelectionRangesRows: "Importar les files de la selecció " + importEmojisButton: "Importar els Emojis marcats" + confirmImportEmojisTitle: "Importar Emojis" + confirmImportEmojisDescription: "Importar {count} Emojis d'una adreça remota. Tingues cura de les llicències dels Emojis. Vols importar-los?" + _local: + tabTitleList: "Llistar els Emojis registrats" + tabTitleRegister: "Registre d'Emojis" + _list: + emojisNothing: "No hi ha Emojis registrats" + markAsDeleteTargetRows: "Files seleccionades que s'han d'esborrar " + markAsDeleteTargetRanges: "Selecció de files per la seva eliminació " + alertUpdateEmojisNothingDescription: "No hi ha Emojis actualitzats." + alertDeleteEmojisNothingDescription: "No hi ha Emoji per esborrar." + confirmMovePage: "Vols canviar de pàgina?" + confirmChangeView: "Vols canviar la pantalla?" + confirmUpdateEmojisDescription: "Actualitzar {count} Emojis. Vols executar-ho?" + confirmDeleteEmojisDescription: "Esborrar {count} Emojis marcats. Vols continuar?" + confirmResetDescription: "Es restabliran tots els canvis fets fins ara." + confirmMovePageDesciption: "S'han fet canvis als Emojis d'aquesta pàgina. Si continues navegant sense guardar els canvis, es perdran tots els canvis fets en aquesta pàgina." + dialogSelectRoleTitle: "Buscar Emojis per rol" + _register: + uploadSettingTitle: "Actualitza la configuració " + 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?" _embedCodeGen: title: "Personalitza el codi per incrustar" header: "Mostrar la capçalera" @@ -2744,3 +2812,34 @@ _selfXssPrevention: _followRequest: recieved: "Sol·licituds rebudes" sent: "Sol·licituds enviades" +_remoteLookupErrors: + _federationNotAllowed: + title: "No es pot establir connexió amb aquest servidor" + description: "És possible que s'hagi desactivat la comunicació amb aquest servidor o que hagi estat bloquejat.\nPosa't en contacte amb l'administrador del servidor." + _uriInvalid: + title: "L'adreça és incorrecte" + description: "Hi ha un problema amb l'adreça introduïda; comprova que no hagis escrit caràcters que no es puguin fer servir." + _requestFailed: + title: "La sol·licitud a fallat" + description: "La comunicació amb aquest servidor a fallat. És possible que l'altre servidor no funcioni. Comprova també que no has posat una adreça no vàlida o inexistent." + _responseInvalid: + title: "La resposta no és correcta " + description: "Hem pogut comunicar-nos amb aquest servidor, però les dades rebudes no són correctes." + _responseInvalidIdHostNotMatch: + description: "El domini de l'adreça introduïda no és el mateix que el domini de l'adreça final obtinguda. Si estàs consultant continguts remots mitjançant servidors tercers, torna a fer la consulta fent servir l'adreça que es pot obtenir en el servidor origen." + _noSuchObject: + title: "No s'ha trobat" + description: "No es pot trobar el recurs sol·licitat, si us plau comprova l'adreça una altra vegada." +_captcha: + verify: "Passar pel CAPTCHA" + testSiteKeyMessage: "Pots comprovar una vista prèvia introduïnt valors de prova per la clau del lloc i la clau secreta. Si vols més informació consulteu la següent pàgina." + _error: + _requestFailed: + title: "Ha fallat la sol·licitud del CAPTCHA" + text: "Si us plau, torna a intentar-ho d'aquí una estona o comprova els ajustos de nou." + _verificationFailed: + title: "Ha fallat la validació CAPTCHA" + text: "Comprova que els ajustos són els correctes." + _unknown: + title: "Error CAPTCHA" + text: "S'ha produït un error inesperat." diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 504ba1f8c86f..20bea96b7fb8 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -2024,3 +2024,6 @@ _moderationLogTypes: createInvitation: "Vygenerovat pozvánku" _reversi: total: "Celkem" +_remoteLookupErrors: + _noSuchObject: + title: "Nenalezeno" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index d85c930b7395..fc62a1a92f62 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -185,7 +185,9 @@ addAccount: "Benutzerkonto hinzufügen" reloadAccountsList: "Benutzerkontoliste aktualisieren" loginFailed: "Anmeldung fehlgeschlagen" showOnRemote: "Auf Ursprungsinstanz ansehen" +continueOnRemote: "Weiter auf Remote-Server" chooseServerOnMisskeyHub: "Wähle einen Server aus dem Misskey Hub" +specifyServerHost: "Server-Host auswählen" inputHostName: "Gib die Domain an" general: "Allgemein" wallpaper: "Hintergrund" @@ -237,6 +239,8 @@ silencedInstances: "Stummgeschaltete Instanzen" silencedInstancesDescription: "Gib die Hostnamen der Instanzen, welche stummgeschaltet werden sollen, durch Zeilenumbrüche getrennt an. Alle Konten dieser Instanzen werden als stummgeschaltet behandelt, können nur noch Follow-Anfragen stellen und wenn nicht gefolgt keine lokalen Konten erwähnen. Blockierte Instanzen sind davon nicht betroffen." mediaSilencedInstances: "Medien-stummgeschaltete Server" mediaSilencedInstancesDescription: "Gib pro Zeile die Hostnamen der Server ein, dessen Medien du stummschalten möchtest. Alle Benutzerkonten der aufgeführten Server werden als sensibel behandelt und können keine benutzerdefinierten Emojis verwenden. Gesperrte Server sind davon nicht betroffen." +federationAllowedHosts: "Föderierte Instanzen" +federationAllowedHostsDescription: "Trage die Hostnamen ein mit den du eine Föderation eingehen möchtest. Trenne mit Zeilenumbruch." muteAndBlock: "Stummschaltungen und Blockierungen" mutedUsers: "Stummgeschaltete Benutzer" blockedUsers: "Blockierte Benutzer" @@ -449,6 +453,7 @@ totpDescription: "Logge dich via Authentifizierungs-App mit Einmalpasswort ein" moderator: "Moderator" moderation: "Moderation" moderationNote: "Moderationsnotiz" +moderationNoteDescription: "Trage hier Notizen ein. Diese sind nur für die Moderatoren sichtbar." addModerationNote: "Moderationsnotiz hinzufügen" moderationLogs: "Moderationsprotokolle" nUsersMentioned: "Von {n} Benutzern erwähnt" @@ -488,6 +493,7 @@ noMessagesYet: "Noch keine Nachrichten vorhanden" newMessageExists: "Du hast eine neue Nachricht" onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden" signinRequired: "Bitte registriere oder melde dich an, um fortzufahren" +signinOrContinueOnRemote: "Um fortzufahren, gehe zu deiner Instanz oder registriere bzw. melde dich an dieser Instanz an. " invitations: "Einladungen" invitationCode: "Einladungscode" checking: "Wird überprüft …" @@ -579,6 +585,7 @@ masterVolume: "Gesamtlautstärke" notUseSound: "Gebe kein Ton aus" useSoundOnlyWhenActive: "Gebe nur Ton aus, wenn Misskey aktiv ist" details: "Details" +renoteDetails: "Renote Details" chooseEmoji: "Emoji auswählen" unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden" recentUsed: "Vor kurzem verwendet" @@ -595,6 +602,7 @@ descendingOrder: "Absteigende Reihenfolge" scratchpad: "Testumgebung" scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen." uiInspector: "UI-Inspektor" +uiInspectorDescription: "Die Liste der UI-Komponenten-Server können im Zwischenspeicher angesehen werden. Die UI-Komponente wird von der Funktion Ui:C: generiert." output: "Ausgabe" script: "Skript" disablePagesScript: "AiScript auf Seiten deaktivieren" @@ -675,7 +683,10 @@ smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden" smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest." testEmail: "Emailversand testen" wordMute: "Wortstummschaltung" -hardWordMute: "Harte Wort-Stummschaltung" +wordMuteDescription: "Minimiert Notizen, die das angegebene Wort oder den angegebenen Ausdruck enthalten. Minimierte Notizen können angezeigt werden, indem du auf sie klickst." +hardWordMute: "Harte Wortstummschaltung" +showMutedWord: "Stummgeschaltete Wörter anzeigen" +hardWordMuteDescription: "Blendet Notizen aus, die das angegebene Wort oder die angegebene Phrase enthalten. Im Gegensatz zur Wortstummschaltung wird die Notiz vollständig ausgeblendet." regexpError: "Fehler in einem regulären Ausdruck" regexpErrorDescription: "Im regulären Ausdruck deiner in Zeile {line} von {tab}en Wortstummschaltungen ist ein Fehler aufgetreten:" instanceMute: "Instanzstummschaltungen" @@ -1086,6 +1097,8 @@ limitWidthOfReaction: "Begrenze die Breite der Reaktion und zeige sie verkleiner noteIdOrUrl: "Notiz-ID oder URL" video: "Video" videos: "Videos" +audio: "Audio" +audioFiles: "Audio" dataSaver: "Datensparmodus" accountMigration: "Kontomigration" accountMoved: "Dieser Benutzer ist zu einem neuen Konto migriert:" @@ -1125,6 +1138,7 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich." options: "Optionen" specifyUser: "Spezifischer Benutzer" +lookupConfirm: "Zustimmen?" failedToPreviewUrl: "Vorschau nicht anzeigbar" update: "Aktualisieren" rolesThatCanBeUsedThisEmojiAsReaction: "Rollen, die dieses Emoji als Reaktion verwenden können" @@ -1194,7 +1208,10 @@ externalServices: "Externe Dienste" sourceCode: "Quellcode" sourceCodeIsNotYetProvided: "Der Quellcode ist noch nicht verfügbar. Kontaktiere den Administrator, um das Problem zu lösen." repositoryUrl: "Repository URL" +repositoryUrlDescription: "Solltest du Misskey so wie es ist verwenden (im unveränderten Quellcode), gebe Folgendes an:\nhttps://github.com/misskey-dev/misskey" repositoryUrlOrTarballRequired: "Wenn du kein Repository veröffentlicht hast, musst du stattdessen einen Tarball bereitstellen. Siehe .config/example.yml für weitere Informationen." +feedback: "Feedback" +feedbackUrl: "Feedback-Website" impressum: "Impressum" impressumUrl: "Impressums-URL" impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend." @@ -1204,6 +1221,7 @@ tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung" avatarDecorations: "Profilbilddekoration" attach: "Anbringen" detach: "Entfernen" +detachAll: "Alles Entfernen" angle: "Winkel" flip: "Umdrehen" showAvatarDecorations: "Profilbilddekoration anzeigen" @@ -1216,18 +1234,27 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" +reloadRequiredToApplySettings: "Eine Aktualisierung ist erforderlich, um die Einstellungen zu übernehmen." remainingN: "Verbleibend: {n}" overwriteContentConfirm: "Bist du sicher, dass du den aktuellen Inhalt überschreiben willst?" seasonalScreenEffect: "Saisonaler Bildschirmeffekt" decorate: "Dekorieren" addMfmFunction: "MFM hinzufügen" enableQuickAddMfmFunction: "Erweiterte MFM-Auswahl anzeigen" +bubbleGame: "Bubble Game" sfx: "Soundeffekte" soundWillBePlayed: "Es wird Ton wiedergegeben" showReplay: "Wiederholung anzeigen" +replay: "Aufzeichnen" +replaying: "Aufzeichnung" +endReplay: "Aufzeichnung verlassen" +copyReplayData: "Aufzeichnung kopieren" ranking: "Rangliste" lastNDays: "Letzten {n} Tage" backToTitle: "Zurück zum Startbildschirm" +hemisphere: "Hemisphäre" +withSensitive: "Zeige \"sensitive Inhalte\" an" +userSaysSomethingSensitive: "{name} sagt etwas mit sensiblem Inhalt." enableHorizontalSwipe: "Wischen, um zwischen Tabs zu wechseln" loading: "Laden" surrender: "Abbrechen" @@ -1240,6 +1267,8 @@ useNativeUIForVideoAudioPlayer: "Browser-Benutzeroberfläche für die Video- und keepOriginalFilename: "Ursprünglichen Dateinamen beibehalten" keepOriginalFilenameDescription: "Wenn diese Einstellung deaktiviert ist, wird der Dateiname beim Hochladen automatisch durch eine zufällige Zeichenfolge ersetzt." noDescription: "Keine Beschreibung vorhanden" +alwaysConfirmFollow: "Folgen immer bestätigen" +inquiry: "Kontakt" tryAgain: "Bitte später erneut versuchen" confirmWhenRevealingSensitiveMedia: "Das Anzeigen von sensiblen Medien bestätigen" sensitiveMediaRevealConfirm: "Es könnte sich um sensible Medien handeln. Möchtest du sie anzeigen?" @@ -1249,29 +1278,41 @@ fromX: "Von {x}" genEmbedCode: "Einbettungscode generieren" noteOfThisUser: "Notizen dieses Benutzers" clipNoteLimitExceeded: "Zu diesem Clip können keine weiteren Notizen hinzugefügt werden." +performance: "Leistung" +modified: "Bearbeitet" discard: "Verwerfen" thereAreNChanges: "Es gibt {n} Änderung(en)" signinWithPasskey: "Mit Passkey anmelden" +unknownWebAuthnKey: "Unbekannter Passkey" passkeyVerificationFailed: "Die Passkey-Verifizierung ist fehlgeschlagen." passkeyVerificationSucceededButPasswordlessLoginDisabled: "Die Verifizierung des Passkeys war erfolgreich, aber die passwortlose Anmeldung ist deaktiviert." messageToFollower: "Nachricht an die Follower" +target: "Speicherort" testCaptchaWarning: "Diese Funktion ist für CAPTCHA-Testzwecke gedacht.\nNicht in einer Produktivumgebung verwenden." prohibitedWordsForNameOfUser: "Verbotene Begriffe für Benutzernamen" prohibitedWordsForNameOfUserDescription: "Wenn eine Zeichenfolge aus dieser Liste im Namen eines Benutzers enthalten ist, wird der Benutzername abgelehnt. Benutzer mit Moderatorenrechten sind von dieser Einschränkung nicht betroffen." yourNameContainsProhibitedWords: "Dein Name enthält einen verbotenen Begriff" yourNameContainsProhibitedWordsDescription: "Der Name enthält eine verbotene Zeichenfolge. Wende dich an deinen Serveradministrator, wenn du diesen Namen verwenden möchtest." +thisContentsAreMarkedAsSigninRequiredByAuthor: "Logge dich ein, um weitere Inhalte von diesem Nutzer zu sehen." +lockdown: "Sperren" pleaseSelectAccount: "Bitte Konto auswählen" availableRoles: "Verfügbare Rollen" +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." _accountSettings: requireSigninToViewContents: "Anmeldung erfordern, um Inhalte anzuzeigen" requireSigninToViewContentsDescription1: "Erfordere eine Anmeldung, um alle Notizen und andere Inhalte anzuzeigen, die du erstellt hast. Dadurch wird verhindert, dass Crawler deine Informationen sammeln." + requireSigninToViewContentsDescription2: "Der Inhalt wird nicht in URL-Vorschauen (OGP), eingebettet in Webseiten oder auf Servern, die keine Zitate unterstützen, angezeigt." requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern." makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar" makeNotesHiddenBefore: "Frühere Notizen privat machen" + makeNotesHiddenBeforeDescription: "" mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden." + notesOlderThanSpecifiedDateAndTime: "Notizen vor einem bestimmtem Datum und Uhrzeit" _abuseUserReport: forward: "Weiterleiten" forwardDescription: "Leite die Meldung an einen entfernten Server als anonymes Systemkonto weiter." + resolve: "lösen" accept: "Akzeptieren" reject: "Ablehnen" _delivery: @@ -1381,6 +1422,7 @@ _serverSettings: fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden." fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen" fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. " + 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." _accountMigration: moveFrom: "Von einem anderen Konto zu diesem migrieren" @@ -1842,6 +1884,7 @@ _channel: notesCount: "{n} Notizen" nameAndDescription: "Name und Beschreibung" nameOnly: "Nur Name" + allowRenoteToExternal: "Renotes und Zitierungen außerhalb des Kanals erlauben" _menuDisplay: sideFull: "Seitlich" sideIcon: "Seitlich (Icons)" @@ -1930,6 +1973,7 @@ _sfx: note: "Notizen" noteMy: "Meine Notizen" notification: "Benachrichtigungen" + reaction: "Auswählen einer Reaktion" _soundSettings: driveFile: "Audiodatei aus dem Drive verwenden" driveFileWarn: "Wähle eine Audiodatei aus dem Drive" @@ -2028,12 +2072,22 @@ _permissions: "read:admin:server-info": "Serverinformationen anzeigen" "read:admin:show-moderation-log": "Moderationsprotokoll einsehen" "read:admin:show-user": "Private Benutzerinformationen einsehen" + "write:admin:roles": "Rollen verwalten" + "read:admin:roles": "Rollen anzeigen" + "write:admin:relays": "Relays verwalten" + "read:admin:relays": "Relays anzeigen" "write:admin:invite-codes": "Einladungscodes verwalten" "read:admin:invite-codes": "Einladungscodes anzeigen" "write:admin:announcements": "Ankündigungen verwalten" "read:admin:announcements": "Ankündigungen einsehen" "write:admin:avatar-decorations": "Kann Avatar-Dekorationen verwalten" "read:admin:avatar-decorations": "Avatar-Dekorationen ansehen" + "write:admin:account": "Benutzerkonten verwalten" + "read:admin:account": "Benutzerkonten anzeigen" + "write:admin:emoji": "Emojis verwalten" + "read:admin:emoji": "Emojis anzeigen" + "write:admin:queue": "Job-Warteschlange verwalten" + "read:admin:queue": "Job-Warteschlange anzeigen" _auth: shareAccessTitle: "Verteilung von App-Berechtigungen" shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?" @@ -2151,6 +2205,8 @@ _profile: changeAvatar: "Profilbild ändern" changeBanner: "Banner ändern" verifiedLinkDescription: "Gibst du hier eine URL ein, die einen Link zu deinem Profile enthält, wird neben diesem Feld ein Icon zur Besitzbestätigung angezeigt." + avatarDecorationMax: "Du kannst bis zu {max} Dekorationen hinzufügen." + followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt." _exportOrImport: allNotes: "Alle Notizen" favoritedNotes: "Als Favorit markierte Notizen" @@ -2283,6 +2339,7 @@ _notification: reactedBySomeUsers: "{n} Benutzer haben eine Reaktion geschickt" renotedBySomeUsers: "Renote von {n} Benutzern" followedBySomeUsers: "Von {n} Benutzern gefolgt" + login: "Neue Anmeldung erfolgt" _types: all: "Alle" note: "Neue Notizen" @@ -2295,6 +2352,7 @@ _notification: pollEnded: "Ende von Umfragen" receiveFollowRequest: "Erhaltene Follow-Anfragen" followRequestAccepted: "Akzeptierte Follow-Anfragen" + roleAssigned: "Rolle zugewiesen" achievementEarned: "Errungenschaft freigeschaltet" login: "Anmelden" app: "Benachrichtigungen von Apps" @@ -2346,6 +2404,7 @@ _webhookSettings: createWebhook: "Webhook erstellen" name: "Name" secret: "Secret" + trigger: "Auslöser" active: "Aktiviert" _events: follow: "Wenn du jemandem folgst" @@ -2357,8 +2416,10 @@ _webhookSettings: mention: "Wenn du erwähnt wirst" _abuseReport: _notificationRecipient: + createRecipient: "Meldungsempfänger hinzufügen" _recipientType: mail: "Email" + keywords: "Schlüsselwort" _moderationLogTypes: createRole: "Rolle erstellt" deleteRole: "Rolle gelöscht" @@ -2393,6 +2454,13 @@ _moderationLogTypes: createAvatarDecoration: "Profilbilddekoration erstellt" updateAvatarDecoration: "Profilbilddekoration aktualisiert" deleteAvatarDecoration: "Profilbilddekoration gelöscht" + unsetUserAvatar: "Profilbild zurückgesetzt" + unsetUserBanner: "Profilbanner zurückgesetzt" + createSystemWebhook: "System-Webhook erstellt" + updateSystemWebhook: "System-Webhook aktualisiert" + deleteSystemWebhook: "System-Webhook gelöscht" + deletePage: "Seite gelöscht" + deleteGalleryPost: "Galeriebeitrag gelöscht" _fileViewer: title: "Dateiinformationen" type: "Dateityp" @@ -2442,6 +2510,10 @@ _externalResourceInstaller: _themeInstallFailed: title: "Das Farbschema konnte nicht installiert werden" description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden." +_hemisphere: + N: "Nördliche Erdhalbkugel" + S: "Südliche Erdhalbkugel" + caption: "Wird in einigen Client-Einstellungen zur Bestimmung der Jahreszeit verwendet." _reversi: blackOrWhite: "Schwarz/Weiß" rules: "Regeln" @@ -2449,17 +2521,26 @@ _reversi: white: "Weiß" total: "Gesamt" _offlineScreen: + title: "Offline - keine Verbindung zum Server möglich" header: "Verbindung zum Server nicht möglich" _urlPreviewSetting: title: "Einstellungen der URL-Vorschau" enable: "URL-Vorschau aktivieren" 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)" + maximumContentLengthDescription: "Wenn die Content-Length diesen Wert überschreitet, wird keine Vorschau erzeugt." + requireContentLength: "Vorschau nur generieren, wenn Content-Length verfügbar ist" + requireContentLengthDescription: "Wenn der Server keine Content-Length zurückgibt, wird keine Vorschau erzeugt." + userAgent: "User-Agent" _mediaControls: playbackRate: "Wiedergabegeschwindigkeit" _contextMenu: title: "Kontextmenü" app: "Anwendung" +_gridComponent: + _error: + requiredValue: "Dieser Wert ist ein Pflichtfeld" _embedCodeGen: title: "Einbettungscode anpassen" header: "Kopfzeile anzeigen" @@ -2476,3 +2557,13 @@ _selfXssPrevention: title: "„Füge in diesen Bereich etwas ein“ ist eine Betrugsmasche." description1: "Wenn du hier etwas einfügst, könnte ein böswilliger Benutzer dein Konto übernehmen oder deine persönlichen Daten stehlen." description3: "Weitere Informationen findest du hier. {link}" +_remoteLookupErrors: + _federationNotAllowed: + title: "Kommunikation mit diesem Server nicht möglich" + description: "Möglicherweise wurde die Kommunikation mit diesem Server deaktiviert oder dieser Server ist blockiert.\nWende dich bitte an den Serveradministrator." + _uriInvalid: + title: "URI ist fehlerhaft" + description: "Es gibt ein Problem mit der von dir eingegebenen URI. Bitte prüfe, ob du Zeichen eingegeben hast, die in der URI nicht verwendet werden können." + _noSuchObject: + title: "Nicht gefunden" + description: "Die angeforderte Ressource konnte nicht gefunden werden, bitte überprüfe die URI erneut." diff --git a/locales/en-US.yml b/locales/en-US.yml index 69e6da1a6f44..2a5010390f9f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -5,6 +5,7 @@ introMisskey: "Welcome! Misskey is an open source, decentralized microblogging s poweredByMisskeyDescription: "{name} is one of the services powered by the open source platform Misskey (referred to as a \"Misskey instance\")." monthAndDay: "{month}/{day}" search: "Search" +reset: "Reset" notifications: "Notifications" username: "Username" password: "Password" @@ -48,6 +49,7 @@ pin: "Pin to profile" unpin: "Unpin from profile" copyContent: "Copy contents" copyLink: "Copy link" +copyRemoteLink: "Copy remote link" copyLinkRenote: "Copy renote link" delete: "Delete" deleteAndEdit: "Delete and edit" @@ -684,11 +686,15 @@ smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecureInfo: "Turn this off when using STARTTLS" testEmail: "Test email delivery" wordMute: "Word mute" +wordMuteDescription: "Minimize notes that contain the specified word or phrase. Minimized notes can be displayed by clicking on them." hardWordMute: "Hard word mute" +showMutedWord: "Show muted words" +hardWordMuteDescription: "Hide notes that contain the specified word or phrase. Unlike word mute, the note will be completely hidden from view." regexpError: "Regular Expression error" regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:" instanceMute: "Instance Mutes" userSaysSomething: "{name} said something" +userSaysSomethingAbout: "{name} said something about \"{word}\"" makeActive: "Activate" display: "Display" copy: "Copy" @@ -1300,6 +1306,9 @@ thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require log lockdown: "Lockdown" pleaseSelectAccount: "Select an account" 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." _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." @@ -1456,6 +1465,8 @@ _serverSettings: reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase." 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." _accountMigration: moveFrom: "Migrate another account to this one" @@ -2718,6 +2729,61 @@ _contextMenu: app: "Application" appWithShift: "Application with shift key" native: "Native" +_gridComponent: + _error: + requiredValue: "This value is required" + columnTypeNotSupport: "Validation with regular expression is supported only for type:text columns." + patternNotMatch: "This value doesn't match the pattern in {pattern}" + notUnique: "This value must be unique" +_roleSelectDialog: + notSelected: "Not selected" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copy selected rows" + copySelectionRanges: "Copy selected ranges" + deleteSelectionRows: "Delete selected rows" + deleteSelectionRanges: "Delete rows in the selection" + searchSettings: "Search settings" + searchSettingCaption: "Set detailed search criteria." + sortOrder: "Sort order" + registrationLogs: "Registration log" + registrationLogsCaption: "Logs will be displayed when updating or deleting Emojis. They will disappear after updating or deleting them, moving to a new page, or reloading." + alertEmojisRegisterFailedDescription: "Failed to update or delete Emojis. Please check the registration log for details." + _logs: + showSuccessLogSwitch: "Show success log" + failureLogNothing: "There is no failure log." + logNothing: "There is no log." + _remote: + selectionRowDetail: "Selected row's detail" + importSelectionRows: "Import selected rows" + importSelectionRangesRows: "Import rows in the selection" + importEmojisButton: "Import checked Emojis" + confirmImportEmojisTitle: "Import Emojis" + confirmImportEmojisDescription: "Import {count} Emoji(s) received from the remote server. Please pay close attention to the license of the Emoji. Are you sure to continue?" + _local: + tabTitleList: "List of registered Emojis" + tabTitleRegister: "Emoji registration" + _list: + emojisNothing: "There are no registered Emojis." + markAsDeleteTargetRows: "Mark selected rows as a target to delete" + 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." + confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?" + confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?" + dialogSelectRoleTitle: "Search by roll 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?" _embedCodeGen: title: "Customize embed code" header: "Show header" @@ -2738,3 +2804,37 @@ _selfXssPrevention: description1: "If you paste something here, a malicious user could hijack your account or steal your personal information." description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window." description3: "For more information, please refer to this. {link}" +_followRequest: + recieved: "Received application" + sent: "Sent application" +_remoteLookupErrors: + _federationNotAllowed: + title: "Unable to communicate with this server" + description: "Communication with this server may have been disabled or this server may be blocked.\nPlease contact the server administrator." + _uriInvalid: + title: "URI is invalid" + description: "There is a problem with the URI you entered. Please check if you entered characters that cannot be used in the URI." + _requestFailed: + title: "Request failed" + description: "Communication with this server failed. The server may be down. Also, please make sure that you have not entered an invalid or nonexistent URI." + _responseInvalid: + title: "Response is invalid" + description: "It could communicate with this server, but the data obtained was incorrect." + _responseInvalidIdHostNotMatch: + description: "The domain of the entered URI differs from the domain of the final obtained URI. If you are looking up remote content through a third-party server, please look up again using a URI that can be obtained from the origin server." + _noSuchObject: + title: "Not found" + description: "The requested resource was not found, please recheck the URI." +_captcha: + verify: "Please verify the CAPTCHA" + testSiteKeyMessage: "You can check the preview by entering the test values for the site and secret keys.\nPlease see the following page for details." + _error: + _requestFailed: + title: "Failed to request CAPTCHA" + text: "Please run it after a while or check the settings again." + _verificationFailed: + title: "Failed to validate CAPTCHA" + text: "Please check again if the settings are correct." + _unknown: + title: "CAPTCHA error" + text: "An unexpected error occurred." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index a4ec114b1522..58331e9664b1 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -10,6 +10,7 @@ username: "Nombre de usuario" password: "Contraseña" initialPasswordForSetup: "Contraseña para iniciar la inicialización" 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" fetchingAsApObject: "Buscando en el fediverso" ok: "OK" @@ -47,6 +48,7 @@ pin: "Fijar al perfil" unpin: "Desfijar" copyContent: "Copiar contenido" copyLink: "Copiar enlace" +copyRemoteLink: "Copiar enlace remoto" copyLinkRenote: "Copiar enlace de renota" delete: "Borrar" deleteAndEdit: "Borrar y editar" @@ -198,6 +200,7 @@ followConfirm: "¿Desea 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" +selectSelf: "Elígete a ti mismo" selectUser: "Elegir usuario" recipient: "Recipiente" annotation: "Anotación" @@ -213,6 +216,7 @@ perDay: "por día" stopActivityDelivery: "Dejar de enviar actividades" blockThisInstance: "Bloquear instancia" silenceThisInstance: "Silenciar esta instancia" +mediaSilenceThisInstance: "Silencia la Multimedia(Imágenes,videos...) para este servidor" operations: "Operaciones" software: "Software" version: "Versión" @@ -234,6 +238,10 @@ 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." 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." +federationAllowedHosts: "Servidores federados" +federationAllowedHostsDescription: "Establezca los nombres de los servidores que pueden federarse, separados por una nueva línea." muteAndBlock: "Silenciar y bloquear" mutedUsers: "Usuarios silenciados" blockedUsers: "Usuarios bloqueados" @@ -324,6 +332,7 @@ selectFile: "Elegir archivo" selectFiles: "Elegir archivos" selectFolder: "Seleccione una carpeta" selectFolders: "Seleccione carpetas" +fileNotSelected: "Archivo no seleccionado." renameFile: "Renombrar archivo" folderName: "Nombre de la carpeta" createFolder: "Crear carpeta" @@ -331,6 +340,7 @@ renameFolder: "Renombrar carpeta" deleteFolder: "Borrar carpeta" folder: "Carpeta" addFile: "Agregar archivo" +showFile: "Examinar archivos" emptyDrive: "El drive está vacío" emptyFolder: "La carpeta está vacía" unableToDelete: "No se puede borrar" @@ -444,6 +454,7 @@ totpDescription: "Ingresa una contaseña de un sólo uso usando la aplicación a moderator: "Moderador" moderation: "Moderación" moderationNote: "Nota de moderación" +moderationNoteDescription: "Puedes rellenar notas que solo se comparten entre moderadores." addModerationNote: "Añadir nota de moderación" moderationLogs: "Log de moderación" nUsersMentioned: "{n} usuarios mencionados" @@ -478,10 +489,12 @@ retype: "Ingrese de nuevo" noteOf: "Notas de {user}" quoteAttached: "Cita añadida" quoteQuestion: "¿Quiere añadir una cita?" +attachAsFileQuestion: "El texto del portapapeles es demasiado grande ¿Desea adjuntarlo como archivo de texto?" noMessagesYet: "Aún no hay chat" newMessageExists: "Tienes un mensaje nuevo" onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje" signinRequired: "Iniciar sesión" +signinOrContinueOnRemote: "Para continuar, tendrá que ir a su servidor o registrarse e iniciar sesión en este servidor" invitations: "Invitar" invitationCode: "Código de invitación" checking: "Comprobando" @@ -505,6 +518,7 @@ emojiStyle: "Estilo de emoji" native: "Nativo" menuStyle: "Diseño del menú" style: "Diseño" +popup: "Ventana emergente" showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor" showReactionsCount: "Mostrar el número de reacciones en las notas" noHistory: "No hay datos en el historial" @@ -572,6 +586,7 @@ masterVolume: "Volumen principal" notUseSound: "Sin sonido" useSoundOnlyWhenActive: "Sonar solo cuando Misskey esté activo" details: "Detalles" +renoteDetails: "Detalles(Renota)" chooseEmoji: "Elije un emoji" unableToProcess: "La operación no se puede llevar a cabo" recentUsed: "Usado recientemente" @@ -587,6 +602,7 @@ ascendingOrder: "Ascendente" descendingOrder: "Descendente" scratchpad: "Scratch pad" scratchpadDescription: "Scratchpad proporciona un entorno experimental para AiScript. Puede escribir, ejecutar y verificar los resultados que interactúan con Misskey." +uiInspector: "Inspector de UI" output: "Salida" script: "Script" disablePagesScript: "Deshabilitar AiScript en Páginas" @@ -1117,6 +1133,7 @@ preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generati 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" specifyUser: "Especificar usuario" +lookupConfirm: "¿Quiere informarse?" failedToPreviewUrl: "No se pudo generar la vista previa" update: "Actualizar" rolesThatCanBeUsedThisEmojiAsReaction: "Roles que pueden usar este emoji como reacción" @@ -1251,6 +1268,11 @@ tryAgain: "Por favor , inténtalo de nuevo" performance: "Rendimiento" unknownWebAuthnKey: "Esto no se ha registrado llave maestra." messageToFollower: "Mensaje a seguidores" +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" +_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." _abuseUserReport: accept: "Acepte" reject: "repudio" @@ -2535,3 +2557,6 @@ _mediaControls: pip: "Picture in Picture" playbackRate: "Velocidad de reproducción" loop: "Reproducción en bucle" +_remoteLookupErrors: + _noSuchObject: + title: "No se encuentra" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index b105a86b5e7a..473774114e2f 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -2364,3 +2364,6 @@ _mediaControls: _embedCodeGen: title: "Personnaliser le code d'intégration" generateCode: "Générer le code d'intégration" +_remoteLookupErrors: + _noSuchObject: + title: "Non trouvé" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index fe3f20761822..9a28cee275ef 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -2610,3 +2610,6 @@ _mediaControls: pip: "Gambar dalam Gambar" playbackRate: "Kecepatan Pemutaran" loop: "Ulangi Pemutaran" +_remoteLookupErrors: + _noSuchObject: + title: "Tidak dapat ditemukan" diff --git a/locales/index.d.ts b/locales/index.d.ts index 0ae188f1f7f8..a0540fd22883 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -36,6 +36,10 @@ export interface Locale extends ILocale { * 検索 */ "search": string; + /** + * リセット + */ + "reset": string; /** * 通知 */ @@ -210,6 +214,10 @@ export interface Locale extends ILocale { * リンクをコピー */ "copyLink": string; + /** + * リモートのリンクをコピー + */ + "copyRemoteLink": string; /** * リノートのリンクをコピー */ @@ -2754,10 +2762,22 @@ export interface Locale extends ILocale { * ワードミュート */ "wordMute": string; + /** + * 指定した語句を含むノートを最小化します。最小化されたノートをクリックすることで表示することができます。 + */ + "wordMuteDescription": string; /** * ハードワードミュート */ "hardWordMute": string; + /** + * ミュートされたワードを表示 + */ + "showMutedWord": string; + /** + * 指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。 + */ + "hardWordMuteDescription": string; /** * 正規表現エラー */ @@ -2774,6 +2794,10 @@ export interface Locale extends ILocale { * {name}が何かを言いました */ "userSaysSomething": ParameterizedString<"name">; + /** + * {name}が「{word}」について何かを言いました + */ + "userSaysSomethingAbout": ParameterizedString<"name" | "word">; /** * アクティブにする */ @@ -5222,6 +5246,14 @@ export interface Locale extends ILocale { * 注意事項を理解した上でオンにします。 */ "acknowledgeNotesAndEnable": string; + /** + * このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。 + */ + "federationSpecified": string; + /** + * このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。 + */ + "federationDisabled": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする @@ -10515,6 +10547,227 @@ export interface Locale extends ILocale { */ "native": string; }; + "_gridComponent": { + "_error": { + /** + * この値は必須項目です + */ + "requiredValue": string; + /** + * 正規表現によるバリデーションはtype:textのカラムのみサポートします。 + */ + "columnTypeNotSupport": string; + /** + * この値は{pattern}のパターンに一致しません + */ + "patternNotMatch": ParameterizedString<"pattern">; + /** + * この値は一意である必要があります + */ + "notUnique": string; + }; + }; + "_roleSelectDialog": { + /** + * 選択されていません + */ + "notSelected": string; + }; + "_customEmojisManager": { + "_gridCommon": { + /** + * 選択行をコピー + */ + "copySelectionRows": string; + /** + * 選択範囲をコピー + */ + "copySelectionRanges": string; + /** + * 選択行を削除 + */ + "deleteSelectionRows": string; + /** + * 選択範囲の値をクリア + */ + "deleteSelectionRanges": string; + /** + * 検索設定 + */ + "searchSettings": string; + /** + * 検索条件を詳細に設定します。 + */ + "searchSettingCaption": string; + /** + * 表示件数 + */ + "searchLimit": string; + /** + * 並び順 + */ + "sortOrder": string; + /** + * 登録ログ + */ + "registrationLogs": string; + /** + * 絵文字更新・削除時のログが表示されます。更新・削除操作を行ったり、ページを遷移・リロードすると消えます。 + */ + "registrationLogsCaption": string; + /** + * 絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。 + */ + "alertEmojisRegisterFailedDescription": string; + }; + "_logs": { + /** + * 成功ログを表示 + */ + "showSuccessLogSwitch": string; + /** + * 失敗ログはありません。 + */ + "failureLogNothing": string; + /** + * ログはありません。 + */ + "logNothing": string; + }; + "_remote": { + /** + * 選択行の詳細 + */ + "selectionRowDetail": string; + /** + * 選択行をインポート + */ + "importSelectionRows": string; + /** + * 選択範囲の行をインポート + */ + "importSelectionRangesRows": string; + /** + * チェックされた絵文字をインポート + */ + "importEmojisButton": string; + /** + * 絵文字のインポート + */ + "confirmImportEmojisTitle": string; + /** + * リモートから受信した{count}個の絵文字のインポートを行います。絵文字のライセンスに十分な注意を払ってください。実行しますか? + */ + "confirmImportEmojisDescription": ParameterizedString<"count">; + }; + "_local": { + /** + * 登録済み絵文字一覧 + */ + "tabTitleList": string; + /** + * 絵文字の登録 + */ + "tabTitleRegister": string; + "_list": { + /** + * 登録された絵文字はありません。 + */ + "emojisNothing": string; + /** + * 選択行を削除対象にする + */ + "markAsDeleteTargetRows": string; + /** + * 選択範囲の行を削除対象にする + */ + "markAsDeleteTargetRanges": string; + /** + * 変更された絵文字はありません。 + */ + "alertUpdateEmojisNothingDescription": string; + /** + * 削除対象の絵文字はありません。 + */ + "alertDeleteEmojisNothingDescription": string; + /** + * ページを移動しますか? + */ + "confirmMovePage": string; + /** + * 表示を変更しますか? + */ + "confirmChangeView": string; + /** + * {count}個の絵文字を更新します。実行しますか? + */ + "confirmUpdateEmojisDescription": ParameterizedString<"count">; + /** + * チェックがつけられた{count}個の絵文字を削除します。実行しますか? + */ + "confirmDeleteEmojisDescription": ParameterizedString<"count">; + /** + * 今までに加えた変更がすべてリセットされます。 + */ + "confirmResetDescription": string; + /** + * このページの絵文字に変更が加えられています。 + * 保存せずにこのままページを移動すると、このページで加えた変更はすべて破棄されます。 + */ + "confirmMovePageDesciption": string; + /** + * 絵文字に設定されたロールで検索 + */ + "dialogSelectRoleTitle": string; + }; + "_register": { + /** + * アップロード設定 + */ + "uploadSettingTitle": string; + /** + * この画面で絵文字アップロードを行う際の動作を設定できます。 + */ + "uploadSettingDescription": string; + /** + * ディレクトリ名を"category"に入力する + */ + "directoryToCategoryLabel": string; + /** + * ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を"category"に入力します。 + */ + "directoryToCategoryCaption": string; + /** + * いずれかの方法で登録する絵文字を選択してください。 + */ + "emojiInputAreaCaption": string; + /** + * この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ + */ + "emojiInputAreaList1": string; + /** + * このリンクをクリックしてPCから選択する + */ + "emojiInputAreaList2": string; + /** + * このリンクをクリックしてドライブから選択する + */ + "emojiInputAreaList3": string; + /** + * リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです) + */ + "confirmRegisterEmojisDescription": ParameterizedString<"count">; + /** + * 編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか? + */ + "confirmClearEmojisDescription": string; + /** + * ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか? + */ + "confirmUploadEmojisDescription": ParameterizedString<"count">; + }; + }; + }; "_embedCodeGen": { /** * 埋め込みコードをカスタマイズ @@ -10601,6 +10854,108 @@ export interface Locale extends ILocale { */ "sent": string; }; + "_remoteLookupErrors": { + "_federationNotAllowed": { + /** + * このサーバーとは通信できません + */ + "title": string; + /** + * このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。 + * サーバー管理者にお問い合わせください。 + */ + "description": string; + }; + "_uriInvalid": { + /** + * URIが不正です + */ + "title": string; + /** + * 入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。 + */ + "description": string; + }; + "_requestFailed": { + /** + * リクエストに失敗しました + */ + "title": string; + /** + * このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。 + */ + "description": string; + }; + "_responseInvalid": { + /** + * レスポンスが不正です + */ + "title": string; + /** + * このサーバーと通信することはできましたが、得られたデータが不正なものでした。 + */ + "description": string; + }; + "_responseInvalidIdHostNotMatch": { + /** + * 入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。 + */ + "description": string; + }; + "_noSuchObject": { + /** + * 見つかりません + */ + "title": string; + /** + * 要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。 + */ + "description": string; + }; + }; + "_captcha": { + /** + * CAPTCHAを通過してください + */ + "verify": string; + /** + * サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。 + * 詳細は下記ページをご確認ください。 + */ + "testSiteKeyMessage": string; + "_error": { + "_requestFailed": { + /** + * CAPTCHAのリクエストに失敗しました + */ + "title": string; + /** + * しばらく後に実行するか、設定をもう一度ご確認ください。 + */ + "text": string; + }; + "_verificationFailed": { + /** + * CAPTCHAの検証に失敗しました + */ + "title": string; + /** + * 設定が正しいかどうかもう一度確認ください。 + */ + "text": string; + }; + "_unknown": { + /** + * CAPTCHAエラー + */ + "title": string; + /** + * 想定外のエラーが発生しました。 + */ + "text": string; + }; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 66ca935b1bd9..370967643671 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -5,6 +5,7 @@ introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source Misskey." monthAndDay: "{day}/{month}" search: "Cerca" +reset: "Ripristinare" notifications: "Notifiche" username: "Nome utente" password: "Password" @@ -48,6 +49,7 @@ pin: "Fissa sul profilo" unpin: "Non fissare sul profilo" copyContent: "Copia il contenuto" copyLink: "Copia il link" +copyRemoteLink: "Copia link remoto" copyLinkRenote: "Copia collegamento alla Rinota" delete: "Elimina" deleteAndEdit: "Elimina e modifica" @@ -56,7 +58,7 @@ addToList: "Aggiungi alla lista" addToAntenna: "Aggiungi all'antenna" sendMessage: "Invia messaggio" copyRSS: "Copia RSS" -copyUsername: "Copia nome utente" +copyUsername: "Copia indirizzo del profilo" copyUserId: "Copia ID del profilo" copyNoteId: "Copia ID della Nota" copyFileId: "Copia ID del file" @@ -440,7 +442,7 @@ recentlyRegisteredUsers: "Profili iscritti di recente" recentlyDiscoveredUsers: "Profili scoperti di recente" exploreUsersCount: "Ci sono {count} profili" exploreFediverse: "Esplora il Fediverso" -popularTags: "Tag di tendenza" +popularTags: "Hashtag popolari" userList: "Liste" about: "Informazioni" aboutMisskey: "Informazioni di Misskey" @@ -551,8 +553,8 @@ promote: "Pubblicizza" numberOfDays: "Numero di giorni" hideThisNote: "Nasconda la nota" showFeaturedNotesInTimeline: "Mostrare le note di tendenza nella tua timeline" -objectStorage: "Stoccaggio oggetti" -useObjectStorage: "Utilizza stoccaggio oggetti" +objectStorage: "Storage S3" +useObjectStorage: "Utilizza lo storage S3 in cloud" objectStorageBaseUrl: "Base URL" objectStorageBaseUrlDesc: "URL di riferimento. In caso di utilizzo di proxy o CDN l'URL è 'https://.s3.amazonaws.com' per S3, 'https://storage.googleapis.com/' per GCS eccetera. " objectStorageBucket: "Bucket" @@ -586,6 +588,7 @@ masterVolume: "Volume principale" notUseSound: "Non emettere suoni" useSoundOnlyWhenActive: "Emetti suoni solo quando Misskey è in attività" details: "Dettagli" +renoteDetails: "Dettagli della Rinota" chooseEmoji: "Scegli emoji" unableToProcess: "Impossibile compiere l'operazione" recentUsed: "Usato di recente" @@ -683,11 +686,15 @@ 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" +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." 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}\"" makeActive: "Attiva" display: "Visualizza" copy: "Copia" @@ -699,7 +706,7 @@ database: "Base dati" channel: "Canale" create: "Crea" notificationSetting: "Impostazioni notifiche" -notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare." +notificationSettingDesc: "Scegli quali notifiche mostrare." useGlobalSetting: "Usa impostazioni generali" useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifiche del profilo. Altrimenti si possono segliere impostazioni personalizzate." other: "Eccetera" @@ -906,7 +913,7 @@ itsOn: "Abilitato" itsOff: "Disabilitato" on: "Acceso" off: "Spento" -emailRequiredForSignup: "L'ndirizzo e-mail è obbligatorio per registrarsi" +emailRequiredForSignup: "L'indirizzo e-mail è obbligatorio per registrarsi" unread: "Non lette" filter: "Filtri" controlPanel: "Pannello di controllo" @@ -969,7 +976,7 @@ requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema" typeToConfirm: "Digita {x} per continuare" deleteAccount: "Eliminazione profilo" -document: "Documento" +document: "Documentazione" numberOfPageCache: "Numero di pagine cache" numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria." logoutConfirm: "Vuoi davvero uscire da Misskey? " @@ -1105,7 +1112,7 @@ accountMovedShort: "Questo profilo è stato migrato" operationForbidden: "Operazione non consentita" forceShowAds: "Mostra sempre i banner" addMemo: "Aggiungi Memo" -editMemo: "Modifica Memo" +editMemo: "Modifica il promemoria" reactionsList: "Chi ha reagito?" renotesList: "Chi ha Rinotato?" notificationDisplay: "Stile delle notifiche" @@ -1139,7 +1146,7 @@ options: "Opzioni del ruolo" specifyUser: "Profilo specifico" lookupConfirm: "Vuoi davvero richiedere informazioni?" openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?" -specifyHost: "Specifica l'host" +specifyHost: "Host specifici" failedToPreviewUrl: "Anteprima non disponibile" update: "Aggiorna" rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione" @@ -1239,7 +1246,7 @@ code: "Codice" reloadRequiredToApplySettings: "Per applicare le impostazioni, occorre ricaricare." remainingN: "Rimangono: {n}" overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?" -seasonalScreenEffect: "Schermate in base alla stagione" +seasonalScreenEffect: "Abilita gli effetti speciali stagionali" decorate: "Decora" addMfmFunction: "Aggiungi decorazioni" enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM" @@ -1298,6 +1305,10 @@ yourNameContainsProhibitedWordsDescription: "Se desideri comunque utilizzare que thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autore richiede di iscriversi per vedere il contenuto" lockdown: "Isolamento" pleaseSelectAccount: "Per favore, seleziona un profilo" +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." _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." @@ -1454,6 +1465,8 @@ _serverSettings: 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." 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." _accountMigration: moveFrom: "Migra un altro profilo dentro a questo" @@ -1923,7 +1936,7 @@ _channel: edit: "Gerisci canale" setBanner: "Scegli intestazione" removeBanner: "Rimuovi intestazione" - featured: "Di tendenza" + featured: "Popolari nel canale" owned: "I miei canali" following: "Following" usersCount: "{n} partecipanti" @@ -2200,7 +2213,7 @@ _widgets: notifications: "Notifiche" timeline: "Timeline" calendar: "Calendario" - trends: "Di tendenza" + trends: "Hashtag popolari" clock: "Orologio" rss: "Lettura RSS" rssTicker: "Nastro RSS" @@ -2440,13 +2453,13 @@ _notification: quote: "Cita" reaction: "Reazioni" pollEnded: "Sondaggio chiuso." - receiveFollowRequest: "Richiesta di follow ricevuta" - followRequestAccepted: "Richiesta di follow accettata" + receiveFollowRequest: "Richieste di follow in arrivo" + followRequestAccepted: "Richieste di follow accettate" roleAssigned: "Ruolo concesso" achievementEarned: "Risultato raggiunto" exportCompleted: "Esportazione completata" - login: "Accedi" - test: "Prova la notifica" + login: "Accessi" + test: "Notifiche di test" app: "Notifiche da applicazioni" _actions: followBack: "Following ricambiato" @@ -2716,6 +2729,66 @@ _contextMenu: app: "Applicazione" appWithShift: "Applicazione Shift+Tasto" native: "Interfaccia utente del browser" +_gridComponent: + _error: + requiredValue: "Campo obbligatorio" + columnTypeNotSupport: "Solo le colonne type:text permettono la convalida delle Espresioni Regolari" + patternNotMatch: "Il valore non coincide con {pattern}" + notUnique: "Il valore deve essere univoco" +_roleSelectDialog: + notSelected: "Niente selezioato" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Copia le righe selezionate" + copySelectionRanges: "Copia l'intervallo selezionato" + deleteSelectionRows: "Elimina le righe selezionate" + deleteSelectionRanges: "Elimina le righe nell'intervallo selezionato" + searchSettings: "Impostazioni di ricerca" + searchSettingCaption: "Imposta condizioni di ricerca dettagliate." + searchLimit: "Risultati visualizzati" + sortOrder: "Ordine" + registrationLogs: "Storico della registrazione" + registrationLogsCaption: "Lo storico verrà visualizzato in base alla attività sulle emoji. Scompare quando si esegue un'operazione di aggiornamento/eliminazione o si modifica/ricarica la pagina." + alertEmojisRegisterFailedDescription: "Attenzione, è impossibile modificare la emoji. Si prega di controllare lo storico per ulteriori dettagli." + _logs: + showSuccessLogSwitch: "Mostra le azioni a buon fine" + failureLogNothing: "Non ci sono errori nello storico delle emoji" + logNothing: "Lo storico è vuoto." + _remote: + selectionRowDetail: "Dettagli della riga selezionata" + importSelectionRows: "Importa le righe selezionate" + importSelectionRangesRows: "Importa le righe nell'intervallo selezionato" + importEmojisButton: "Importa le emoji selezionate" + confirmImportEmojisTitle: "Importazione emoji" + confirmImportEmojisDescription: "Importazione di {count} emoji ricevute da remoto. Si prega di prestare molta attenzione al tipo di licenza delle emoji. Vuoi confermare?" + _local: + tabTitleList: "Elenco delle emoji registrate" + tabTitleRegister: "Registrazione emoji" + _list: + emojisNothing: "Non ci sono emoji registrate." + markAsDeleteTargetRows: "Selezionare le righe come eliminabili" + markAsDeleteTargetRanges: "Selezionare le righe nell'intervallo come eliminabili" + alertUpdateEmojisNothingDescription: "Non ci sono emoji aggiornate." + alertDeleteEmojisNothingDescription: "Non ci sono emoji da eliminare." + confirmMovePage: "Vuoi davvero spostare la pagina?" + confirmChangeView: "Vuoi davvero cambiare la vista?" + confirmUpdateEmojisDescription: "Aggiornamento di {count} emoji. Vuoi davvero continuare?" + confirmDeleteEmojisDescription: "Eliminazione delle {count} emoji selezionate. Vuoi davvero continuare?" + confirmResetDescription: "Verranno ripristinate tutte le modifiche apportate finora." + confirmMovePageDesciption: "Sono state modificate le emoji in questa pagina.\nUscendo senza salvare, tutte le modifiche verranno ignorate." + dialogSelectRoleTitle: "Cerca emoji per ruolo" + _register: + uploadSettingTitle: "Caricamento impostazioni" + 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: "Trascina 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" header: "Mostra la testata" @@ -2736,3 +2809,37 @@ _selfXssPrevention: description1: "Incollando qualcosa qui, malintenzionati potrebbero prendere il controllo del tuo profilo o rubare i tuoi dati personali." description2: "Se non sai esattamente cosa stai facendo, %c smetti subito e chiudi questa finestra." description3: "Per favore, controlla questo collegamento per avere maggiori dettagli. {link}" +_followRequest: + recieved: "Ricezione richiesta di Follow" + sent: "Richiesta di Follow, inviata" +_remoteLookupErrors: + _federationNotAllowed: + title: "Server irraggiungibile" + description: "La comunicazione con questo server potrebbe essere disattivata. Hai bloccato il server? Oppure potrebbero averlo bloccato gli amministratori. Contattali per ulteriori informazioni." + _uriInvalid: + title: "URL non valido" + description: "Controlla che l'indirizzo sia valido e sia privo di caratteri non validi." + _requestFailed: + title: "Richiesta fallita" + description: "La comunicazione col server non è riuscita. Potrebbe essere inattivo. Assicurati anche che la URL sia valida." + _responseInvalid: + title: "Risposta non valida" + description: "La comunicazione col server è andata a buon fine, ma abbiamo ricevuto dati non validi." + _responseInvalidIdHostNotMatch: + description: "L'indirizzo immesso non coincide con la URL finale. Interrogando i server per un contenuto remoto, assicurarsi di utilizzare la URL finale e non quella di un server intermedio." + _noSuchObject: + title: "Non trovato" + description: "La risorsa richiesta non è stata trovata. Verificare nuovamente la URL." +_captcha: + verify: "Per favore, controlla la verifica CAPTCHA" + testSiteKeyMessage: "Puoi provare l'anteprima inserendo valori di test, sia per la chiave del sito che per la chiave segreta.\nSi prega di controllare la pagina qui sotto per i dettagli." + _error: + _requestFailed: + title: "Errore durante la richiesta del CAPTCHA" + text: "Riprova più tardi o controlla nuovamente le impostazioni." + _verificationFailed: + title: "Convalida CAPTCHA non riuscita" + text: "Si prega di verificare nuovamente se le impostazioni sono corrette." + _unknown: + title: "Errore CAPTCHA" + text: "Si è verificato un errore imprevisto." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1b59708d8530..a578704434ea 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -5,6 +5,7 @@ introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マ poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォームMisskeyのサーバーのひとつです。" monthAndDay: "{month}月 {day}日" search: "検索" +reset: "リセット" notifications: "通知" username: "ユーザー名" password: "パスワード" @@ -48,6 +49,7 @@ pin: "ピン留め" unpin: "ピン留め解除" copyContent: "内容をコピー" copyLink: "リンクをコピー" +copyRemoteLink: "リモートのリンクをコピー" copyLinkRenote: "リノートのリンクをコピー" delete: "削除" deleteAndEdit: "削除して編集" @@ -684,11 +686,15 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する" smtpSecureInfo: "STARTTLS使用時はオフにします。" testEmail: "配信テスト" wordMute: "ワードミュート" +wordMuteDescription: "指定した語句を含むノートを最小化します。最小化されたノートをクリックすることで表示することができます。" hardWordMute: "ハードワードミュート" +showMutedWord: "ミュートされたワードを表示" +hardWordMuteDescription: "指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。" regexpError: "正規表現エラー" regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:" instanceMute: "サーバーミュート" userSaysSomething: "{name}が何かを言いました" +userSaysSomethingAbout: "{name}が「{word}」について何かを言いました" makeActive: "アクティブにする" display: "表示" copy: "コピー" @@ -1301,6 +1307,8 @@ lockdown: "ロックダウン" pleaseSelectAccount: "アカウントを選択してください" availableRoles: "利用可能なロール" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" +federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。" +federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" @@ -2801,6 +2809,69 @@ _contextMenu: appWithShift: "Shiftキーでアプリケーション" native: "ブラウザのUI" +_gridComponent: + _error: + requiredValue: "この値は必須項目です" + columnTypeNotSupport: "正規表現によるバリデーションは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: "このページの絵文字に変更が加えられています。\n保存せずにこのままページを移動すると、このページで加えた変更はすべて破棄されます。" + dialogSelectRoleTitle: "絵文字に設定されたロールで検索" + _register: + uploadSettingTitle: "アップロード設定" + uploadSettingDescription: "この画面で絵文字アップロードを行う際の動作を設定できます。" + directoryToCategoryLabel: "ディレクトリ名を\"category\"に入力する" + directoryToCategoryCaption: "ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を\"category\"に入力します。" + emojiInputAreaCaption: "いずれかの方法で登録する絵文字を選択してください。" + emojiInputAreaList1: "この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ" + emojiInputAreaList2: "このリンクをクリックしてPCから選択する" + emojiInputAreaList3: "このリンクをクリックしてドライブから選択する" + confirmRegisterEmojisDescription: "リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)" + confirmClearEmojisDescription: "編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?" + confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?" + _embedCodeGen: title: "埋め込みコードをカスタマイズ" header: "ヘッダーを表示" @@ -2826,3 +2897,36 @@ _selfXssPrevention: _followRequest: recieved: "受け取った申請" sent: "送った申請" + +_remoteLookupErrors: + _federationNotAllowed: + title: "このサーバーとは通信できません" + description: "このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。\nサーバー管理者にお問い合わせください。" + _uriInvalid: + title: "URIが不正です" + description: "入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。" + _requestFailed: + title: "リクエストに失敗しました" + description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。" + _responseInvalid: + title: "レスポンスが不正です" + description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。" + _responseInvalidIdHostNotMatch: + description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。" + _noSuchObject: + title: "見つかりません" + description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。" + +_captcha: + verify: "CAPTCHAを通過してください" + testSiteKeyMessage: "サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。\n詳細は下記ページをご確認ください。" + _error: + _requestFailed: + title: "CAPTCHAのリクエストに失敗しました" + text: "しばらく後に実行するか、設定をもう一度ご確認ください。" + _verificationFailed: + title: "CAPTCHAの検証に失敗しました" + text: "設定が正しいかどうかもう一度確認ください。" + _unknown: + title: "CAPTCHAエラー" + text: "想定外のエラーが発生しました。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index c3e00969261e..2dd22207912b 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -15,7 +15,7 @@ forgotPassword: "パスワード忘れたん?" fetchingAsApObject: "今ちと連合に照会しとるで" ok: "ええで" gotIt: "ほい" -cancel: "やめとく" +cancel: "やめる" noThankYou: "やめとく" enterUsername: "ユーザー名を入れてや" renotedBy: "{user}がリノートしたで" @@ -26,7 +26,7 @@ settings: "設定" notificationSettings: "通知の設定" basicSettings: "基本設定" otherSettings: "ほかの設定" -openInWindow: "ウィンドウで開くで" +openInWindow: "ウィンドウで開く" profile: "プロフィール" timeline: "タイムライン" noAccountDescription: "自己紹介食ってもた" @@ -45,7 +45,7 @@ favorited: "お気に入りに入れたで。" alreadyFavorited: "もうお気に入りに入れとるがな。" cantFavorite: "アカン、お気に入りに入れれんかったわ。" pin: "ピン留めしとく" -unpin: "やっぱピン留めせん" +unpin: "ピン留めやめる" copyContent: "内容をコピー" copyLink: "リンクをコピー" copyLinkRenote: "リノートのリンクをコピーするで?" @@ -63,7 +63,7 @@ copyFileId: "ファイルIDをコピー" copyFolderId: "フォルダーIDをコピー" copyProfileUrl: "プロフィールURLをコピー" searchUser: "ユーザーを探す" -searchThisUsersNotes: "ユーザーのノートを検索" +searchThisUsersNotes: "ユーザーのノートを探す" reply: "返事" loadMore: "まだまだあるで!" showMore: "まだまだあるで!" @@ -138,8 +138,8 @@ reactionSettingDescription2: "ドラッグで並び替え、クリックで削 rememberNoteVisibility: "公開範囲覚えといて" attachCancel: "のっけるのやめる" deleteFile: "ファイルをほかす" -markAsSensitive: "ちょっとこれはアカン" -unmarkAsSensitive: "そこまでアカンことないやろ" +markAsSensitive: "ちょっと見せられへんわ" +unmarkAsSensitive: "別にええんじゃね?" enterFileName: "ファイル名を入れてや" mute: "ミュート" unmute: "ミュートやめたる" @@ -152,13 +152,13 @@ unsuspend: "溶かす" blockConfirm: "ブロックしてもええんか?" unblockConfirm: "ブロックやめたるってほんまか?" suspendConfirm: "凍結してしもうてええか?" -unsuspendConfirm: "解凍するけどええか?" +unsuspendConfirm: "溶かしたるけどええか?" selectList: "リストを選ぶ" editList: "リストいじる" selectChannel: "チャンネルを選ぶ" selectAntenna: "アンテナを選ぶ" editAntenna: "アンテナいじる" -createAntenna: "アンテナを作成" +createAntenna: "アンテナを作る" selectWidget: "ウィジェットを選ぶ" editWidgets: "ウィジェットをいじる" editWidgetsExit: "いじるのをやめる" @@ -172,12 +172,12 @@ settingGuide: "ええ感じの設定" cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFilesDescription: "この設定を入れとったら、リモートのファイルを端から端までこのサーバーのキャッシュん中突っ込むようになるで。画像映し出すんがめっちゃ速うなるけど、サーバーの容量をやたらと食うようになるで。リモートの人がどんだけ長くキャッシュを持っとくかはドライブ容量の制限で決めとくで。制限を超えたら古いのから順々に消してって、かわりにリンクになるで。この設定を切ったら、リモートのファイルは最初っからリンクとして扱うことにするけど、画像のサムネ作るのとかみんなのプライバシー守るために、default.ymlのproxyRemoteFilesをtrueにしといたほうがええよ。" youCanCleanRemoteFilesCache: "ファイル管理にある🗑️ボタンでキャッシュ全部ほかすで。" -cacheRemoteSensitiveFiles: "リモートのきわどいファイルをキャッシュに突っ込む" +cacheRemoteSensitiveFiles: "リモートのきわどいファイルをキャッシュする" cacheRemoteSensitiveFilesDescription: "この設定を切ると、リモートのきわどいファイルはキャッシュせず直でリンクするようになるで。" flagAsBot: "Botにするで" flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Misskeyのシステム上での扱いがBotに合ったもんになるからな。" flagAsCat: "猫や。かわええな。" -flagAsCatDescription: "ネコになりたいんならこれつけとき。" +flagAsCatDescription: "猫になりたいんならこれつけとき。" flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで" flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" @@ -186,9 +186,9 @@ reloadAccountsList: "アカウントリストの情報を更新" loginFailed: "ログインに失敗してもうた…" showOnRemote: "リモートで見る" continueOnRemote: "リモートで続行" -chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択" +chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選ぶ" specifyServerHost: "サーバーのドメインを直接指定" -inputHostName: "ドメインを入力せえや" +inputHostName: "ドメインを入力してや" general: "全般" wallpaper: "壁紙" setWallpaper: "壁紙を設定" @@ -586,6 +586,7 @@ masterVolume: "全体のやかましさ" notUseSound: "音出さへん" useSoundOnlyWhenActive: "Misskeyがアクティブなときだけ音出す" details: "もっと" +renoteDetails: "リノートの詳細" chooseEmoji: "絵文字を選ぶ" unableToProcess: "なんか奥の方で詰まってもうた" recentUsed: "最近使ったやつ" @@ -946,6 +947,9 @@ oneHour: "1時間" oneDay: "1日" oneWeek: "1週間" oneMonth: "1ヶ月" +threeMonths: "3ヶ月" +oneYear: "1年" +threeDays: "3日" reflectMayTakeTime: "反映されるまで時間がかかることがあるで" failedToFetchAccountInformation: "アカウントの取得に失敗したみたいや…" rateLimitExceeded: "レート制限が超えたみたいやで" @@ -1292,6 +1296,23 @@ prohibitedWordsForNameOfUser: "禁止ワード(ユーザー名)" prohibitedWordsForNameOfUserDescription: "このリストの中にある文字列がユーザー名に入っとったら、その名前に変更できひんようになるで。モデレーター権限があるユーザーは除外や。" yourNameContainsProhibitedWords: "その名前は禁止した文字列が含まれとるで" yourNameContainsProhibitedWordsDescription: "その名前は禁止した文字列が含まれとるわ。どうしてもって言うなら、サーバー管理者に言うしかないで。" +thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者が、表示にログインが要るって設定してるで" +lockdown: "ロックダウン" +pleaseSelectAccount: "アカウント選んでや" +availableRoles: "使えるロール" +acknowledgeNotesAndEnable: "注意事項をわかった上でオンにする。" +_accountSettings: + requireSigninToViewContents: "ログインしてもらってからコンテンツ見てもらう" + requireSigninToViewContentsDescription1: "あなたが作成した全部のノートとかのコンテンツを見れるようにするのにログインがいるようにするで。クローラーにいろいろ収集されるんを防げるかもしれん。" + requireSigninToViewContentsDescription2: "URLプレビュー(OGP)、Webページへの埋め込み、ノートの引用に対応してないサーバーからの表示ができんくなるで。" + requireSigninToViewContentsDescription3: "リモートサーバーに連合されたコンテンツは、これらの制限が適用されんかもしれんで。" + makeNotesFollowersOnlyBefore: "昔のノートをフォロワーだけに見てもらう" + makeNotesFollowersOnlyBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがフォロワーのみ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" + makeNotesHiddenBefore: "昔のノートを見れんようにする" + makeNotesHiddenBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがフォロワーのみ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" + mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばんかもしれん。" + notesHavePassedSpecifiedPeriod: "決めた時間が経ったノート" + notesOlderThanSpecifiedDateAndTime: "決めた日時より前のノート" _abuseUserReport: forward: "転送" forwardDescription: "匿名のシステムアカウントってことにして、リモートサーバーに通報を転送するで。" @@ -1436,6 +1457,8 @@ _serverSettings: reactionsBufferingDescription: "有効にしたら、リアクション作るときのパフォーマンスがすっごい上がって、データベースへの負荷が減るで。代わりに、Redisのメモリ使用は増えるで。" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定するで。" + openRegistration: "アカウントの作成をオープンにする" + openRegistrationWarning: "登録を解放するのはリスクが伴うで。サーバーをいっつも監視して、なんか起きたらすぐに対応できるんやったら、オンにしてもええと思う。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターがおらんかったら、スパムを防ぐためにこの設定は勝手に切られるで。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに引っ越す" @@ -2156,8 +2179,11 @@ _auth: permissionAsk: "このアプリは次の権限を要求しとるで" pleaseGoBack: "アプリケーションに戻ってええよ" callback: "アプリケーションに戻っとるで" + accepted: "アクセスを許可したで" denied: "アクセスを拒否ったで" + scopeUser: "以下のユーザーとしていじってるで" pleaseLogin: "アプリにアクセスさせるんやったら、ログインしてや。" + byClickingYouWillBeRedirectedToThisUrl: "アクセスを許したら、自動で下のURLに遷移するで" _antennaSources: all: "みんなのノート" homeTimeline: "フォローしとるユーザーのノート" @@ -2709,3 +2735,30 @@ _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とか、ありえんURIを入れてないか確かめて。" + _responseInvalid: + title: "レスポンスがおかしいで" + description: "このサーバーと通信することはできたけど、もらったデータがおかしかったで。" + _responseInvalidIdHostNotMatch: + description: "入力されたURIのドメインと最終的に得られたURIのドメインとが違うで。第三者のサーバーを介してリモートのコンテンツを照会してるんやったら、発信元のサーバーで取得できるURIを使って照会し直して。" + _noSuchObject: + title: "見つからへんね" + description: "求められたリソースが見つからんかったで。URIをもっかい確かめてや。" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 60b82d5db92a..4b9650b636ea 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -840,3 +840,6 @@ _reversi: black: "꺼멍" white: "허영" total: "합게" +_remoteLookupErrors: + _noSuchObject: + title: "몬 찾앗십니다" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index d694d2dbaed4..93883f31cb30 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -5,6 +5,7 @@ introMisskey: "환영합니다! Misskey는 오픈 소스 분산형 마이크로 poweredByMisskeyDescription: "{name} 서버는 오픈소스 플랫폼 Misskey의 서버 가운데 하나입니다." monthAndDay: "{month}월 {day}일" search: "검색" +reset: "초기화" notifications: "알림" username: "유저명" password: "비밀번호" @@ -48,6 +49,7 @@ pin: "프로필에 고정" unpin: "프로필에서 고정 해제" copyContent: "내용 복사" copyLink: "링크 복사" +copyRemoteLink: "리모트 서버의 링크로 복사하기" copyLinkRenote: "리노트 링크 복사" delete: "삭제" deleteAndEdit: "삭제 후 편집" @@ -684,11 +686,15 @@ smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용" smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다." testEmail: "이메일 전송 테스트" wordMute: "단어 뮤트" +wordMuteDescription: "정해진 단어가 포함된 노트를 최소화 한 상태로 표시합니다. 최소화 된 노트는 클릭해서 표시할 수 있습니다." hardWordMute: "하드 단어 뮤트" +showMutedWord: "뮤트한 단어를 표시하기" +hardWordMuteDescription: "정한 단어가 들어간 노트를 숨깁니다. 단어 뮤트와 차이점은 노트가 아예 보이지 않습니다." regexpError: "정규 표현식 오류" regexpErrorDescription: "{tab}단어 뮤트 {line}행의 정규 표현식에 오류가 발생했습니다:" instanceMute: "서버 뮤트" userSaysSomething: "{name}님이 무언가를 말했습니다" +userSaysSomethingAbout: "{name}님이 \"{word}\"를 언급했습니다." makeActive: "활성화" display: "보기" copy: "복사" @@ -1301,13 +1307,15 @@ lockdown: "잠금" pleaseSelectAccount: "계정을 선택해주세요." availableRoles: "사용 가능한 역할" acknowledgeNotesAndEnable: "활성화 하기 전에 주의 사항을 확인했습니다." +federationSpecified: "이 서버는 화이트 리스트 제도로 운영 중 입니다. 정해진 리모트 서버가 아닌 경우 연합되지 않습니다." +federationDisabled: "이 서버는 연합을 하지 않고 있습니다. 리모트 서버 유저와 통신을 할 수 없습니다." _accountSettings: - requireSigninToViewContents: "콘텐츠 열람을 위해 로그인으 필수로 설정하기" + requireSigninToViewContents: "콘텐츠 열람을 위해 로그인을 필수로 설정하기" requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다." requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페이지에 삽입, 노트 인용을 지원하지 않는 서버에서 볼 수 없게 됩니다." requireSigninToViewContentsDescription3: "원격 서버에 연합된 콘텐츠에는 이러한 제한이 적용되지 않을 수 있습니다." makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 볼 수 있도록 설정하기" - makeNotesFollowersOnlyBeforeDescription: "이 기능이 활성화되어 있는 동안, 설정된 날짜 및 시간보다 과거 또는 설정된 시간이 지난 노트는 팔로워만 볼 수 있게 됩니다.비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." + makeNotesFollowersOnlyBeforeDescription: "이 기능이 활성화되어 있는 동안, 설정된 날짜 및 시간보다 과거 또는 설정된 시간이 지난 노트는 팔로워만 볼 수 있게 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." makeNotesHiddenBefore: "과거 노트 비공개로 전환하기" makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다." @@ -2514,7 +2522,7 @@ _webhookSettings: reaction: "누군가 내 노트에 리액션했을 때" mention: "누군가 나를 멘션했을 때" _systemEvents: - abuseReport: "유저롭" + abuseReport: "유저로부터 신고를 받았을 때" abuseReportResolved: "받은 신고를 처리했을 때" userCreated: "유저가 생성되었을 때" inactiveModeratorsWarning: "모더레이터가 일정 기간동안 활동하지 않은 경우" @@ -2721,6 +2729,66 @@ _contextMenu: app: "애플리케이션" appWithShift: "Shift 키로 애플리케이션" native: "브라우저의 UI" +_gridComponent: + _error: + requiredValue: "이 값은 필수 항목입니다." + columnTypeNotSupport: "정규표현 규칙이 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: "이 페이지의 이모지에 변경이 있습니다.\n저장하지 않은 상태로 페이지를 이동하면, 이 페이지에서 바꾼 변경 내용이 모두 지워집니다." + dialogSelectRoleTitle: "이모지에 설정된 역할을 검색" + _register: + uploadSettingTitle: "업로드 설정" + uploadSettingDescription: "여기서 이모지를 업로드 할 때의 동작을 설정할 수 있습니다." + directoryToCategoryLabel: "디렉토리 이름을 \"category\"로 입력하기" + directoryToCategoryCaption: "디렉토리를 드래그 앤 드롭한 경우, 디렉토리 이름을 \"category\"로 입력합니다." + emojiInputAreaCaption: "이모지를 등록할 방법을 선택해주세요." + emojiInputAreaList1: "이 틀 안에 이미지 파일 또는 디렉토리를 끌어서 가져오기" + emojiInputAreaList2: "이 링크를 클릭해서 PC에서 선택하기" + emojiInputAreaList3: "이 링크를 클릭해서 드라이브에서 선택하기" + confirmRegisterEmojisDescription: "리스트에 표시되어진 이모지를 새로운 커스텀 이모지로 등록합니다. 실행할까요? (부하를 피하기 위해, 한 번에 등록할 수 있는 이모지는 {count}건까지 입니다.)" + confirmClearEmojisDescription: "편집 내용을 지우고, 목록에 표시되어진 이모지를 지웁니다. 실행할까요?" + confirmUploadEmojisDescription: "드래그 앤 드롭한 {count}개의 파일을 드라이브에 업로드 합니다. 실행할까요?" _embedCodeGen: title: "임베디드 코드를 커스터마이즈" header: "해더를 표시" @@ -2744,3 +2812,34 @@ _selfXssPrevention: _followRequest: recieved: "받은 신청" sent: "보낸 신청" +_remoteLookupErrors: + _federationNotAllowed: + title: "이 서버와 통신할 수 없음" + description: "이 서버와의 통신이 비활성화 되었거나, 이 서버를 차단 중이거나 서버에게 차단되었을 수 있습니다.\n서버 관리자에게 문의하세요." + _uriInvalid: + title: "URI가 잘못되었습니다." + description: "입력한 URI에 문제가 있습니다. URI에 쓸 수 없는 문자를 넣었는지 확인해보세요." + _requestFailed: + title: "요청을 실패했습니다." + description: "해당 서버와 통신을 실패했습니다. 상대방 서버에 접속 불가능한 상태일 수도 있습니다. 또는 잘못된 URI 또는 없는 URI를 입력했는지 확인해보세요." + _responseInvalid: + title: "유효하지 않은 반응입니다." + description: "이 서버와 통신할 수 있지만, 데이터가 올바르지 않습니다." + _responseInvalidIdHostNotMatch: + description: "입력된 URI과 실제 URI가 다릅니다. 제 3자 서버를 통한 리모트 컨텐츠를 조회하는 경우, 원래 서버 측에서 받아올 수 있는 URI를 사용하여 조회하시길 바랍니다." + _noSuchObject: + title: "찾을 수 없습니다" + description: "요구된 리소스를 찾을 수 없습니다. URI를 다시 한 번 확인해보세요." +_captcha: + verify: "CAPTCHA를 먼저 해결하세요." + testSiteKeyMessage: "사이트 키와 비밀 키에 테스트용 값을 입력하여 미리보기를 확인할 수 있습니다.\n자세한 내용은 아래 페이지를 확인해보세요." + _error: + _requestFailed: + title: "CAPTCHA 요구에 실패했습니다." + text: "잠시 후에 다시 실행하거나, 설정을 다시 한 번 확인해보세요." + _verificationFailed: + title: "CAPTCHA 검증을 실패했습니다." + text: "설정이 올바른지 다시 한 번 확인해보세요." + _unknown: + title: "CAPTCHA 에러" + text: "알 수 없는 에러가 발생했습니다." diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 38965119fe30..2d55c289aa3e 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -474,3 +474,6 @@ _abuseReport: mail: "ອີເມວ" _moderationLogTypes: suspend: "ລະງັບ" +_remoteLookupErrors: + _noSuchObject: + title: "ບໍ່ພົບ" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 7e5e9cbbfb50..685094b4a58a 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -8,6 +8,9 @@ search: "Zoeken" notifications: "Meldingen" username: "Gebruikersnaam" password: "Wachtwoord" +initialPasswordForSetup: "Initiële wachtwoord voor configuratie" +initialPasswordIsIncorrect: "Initiële wachtwoord voor configuratie is onjuist" +initialPasswordForSetupDescription: "Gebruik het initiële wachtwoord uit de configuratie, als je Misskey zelf hebt geïnstalleerd.\nAls je een Misskey hosting provider gebruikt, gebruik dan het gegeven wachtwoord.\nAls je geen wachtwoord hebt gezet, laat het dan leeg om verder te gaan." forgotPassword: "Wachtwoord vergeten" fetchingAsApObject: "Ophalen vanuit de Fediverse" ok: "Ok" @@ -108,9 +111,12 @@ enterEmoji: "Voer een emoji in" renote: "Herdelen" unrenote: "Stop herdelen" renoted: "Herdeeld" +renotedToX: "Renoted naar {name}" cantRenote: "Dit bericht kan niet worden herdeeld" cantReRenote: "Een herdeling kan niet worden herdeeld" quote: "Quote" +renoteToChannel: "Renote naar kanaal" +renoteToOtherChannel: "Renote naar ander kanaal" pinnedNote: "Vastgemaakte notitie" pinned: "Vastmaken aan profielpagina" you: "Jij" @@ -119,6 +125,10 @@ sensitive: "NSFW" add: "Toevoegen" reaction: "Reacties" reactions: "Reacties" +emojiPicker: "Emoji kiezer" +pinnedEmojisForReactionSettingDescription: "Kies de emojis die als eerste getoond worden tijdens het reageren" +pinnedEmojisSettingDescription: "Kies de emojis die als eerste getoond worden tijdens het reageren" +emojiPickerDisplay: "Emoji kiezer weergave" reactionSettingDescription2: "Sleep om opnieuw te ordenen, Klik om te verwijderen, Druk op \"+\" om toe te voegen" rememberNoteVisibility: "Vergeet niet de notitie zichtbaarheidsinstellingen" attachCancel: "Verwijder bijlage" @@ -140,7 +150,7 @@ selectAntenna: "Kies een antenne" selectWidget: "Kies een widget" editWidgets: "Bewerk widgets" editWidgetsExit: "Klaar" -customEmojis: "Maatwerk emoji" +customEmojis: "Eigen emoji" emoji: "Emoji" emojis: "Emoji" emojiName: "Naam emoji" @@ -403,7 +413,31 @@ help: "Help" inputMessageHere: "Voer hier je bericht in" close: "Sluiten" invites: "Uitnodigen" +members: "Leden" +transfer: "Overdracht" +title: "Titel" +text: "Tekst" +enable: "Inschakelen" +next: "Volgende" +retype: "Opnieuw invoeren" +noteOf: "Notitie van {user}" +quoteAttached: "Citaat" +quoteQuestion: "Toevoegen als citaat?" invitations: "Uitnodigen" +dashboard: "Overzicht" +local: "Lokaal" +remote: "Remote" +total: "Totaal" +weekOverWeekChanges: "Wijzigingen sinds vorige week" +dayOverDayChanges: "Dagelijkse wijzigingen" +appearance: "Weergave" +clientSettings: "Clientinstellingen" +accountSettings: "Accountinstellingen" +promotion: "Promotie" +promote: "Promoot" +numberOfDays: "Aantal dagen" +hideThisNote: "Verberg deze notitie" +showFeaturedNotesInTimeline: "Laat featured notities in tijdlijn zien" sound: "Geluid" smtpHost: "Server" smtpUser: "Gebruikersnaam" @@ -501,3 +535,8 @@ _webhookSettings: _moderationLogTypes: suspend: "Opschorten" resetPassword: "Wachtwoord terugzetten" +_reversi: + total: "Totaal" +_remoteLookupErrors: + _noSuchObject: + title: "Niet gevonden" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 87ea01764dde..474e05ba67c3 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -727,3 +727,6 @@ _abuseReport: mail: "E-post" _moderationLogTypes: suspend: "Suspender" +_remoteLookupErrors: + _noSuchObject: + title: "Ikke funnet" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 203f44b334e7..98465ea82b49 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1583,3 +1583,6 @@ _moderationLogTypes: resetPassword: "Zresetuj hasło" _reversi: total: "Łącznie" +_remoteLookupErrors: + _noSuchObject: + title: "Nie znaleziono" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 7ef9e3a94650..aae63805c327 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -8,6 +8,9 @@ search: "Pesquisar" notifications: "Notificações" username: "Nome de usuário" password: "Senha" +initialPasswordForSetup: "Senha para a configuração inicial" +initialPasswordIsIncorrect: "Senha para configuração inicial está incorreta" +initialPasswordForSetupDescription: "Use a senha configurada no arquivo de configuração se você instalou o Misskey manualmente.\nSe você estiver utilizando um serviço de hospedagem, utilize a senha fornecida.\nSe uma senha não foi configurada, deixe em branco e continue." forgotPassword: "Esqueci-me da senha" fetchingAsApObject: "Buscando no Fediverso..." ok: "OK" @@ -196,7 +199,7 @@ followConfirm: "Tem certeza que quer seguir {name}?" proxyAccount: "Conta proxy" proxyAccountDescription: "Uma conta de proxy é uma conta que assume o acompanhamento remoto de um usuário sob certas condições específicas. Por exemplo, quando um usuário inclui um usuário remoto em uma lista, mas ninguém na lista está seguindo o usuário remoto, a atividade não é entregue ao servidor. Nesse caso, a conta de proxy entra em ação para seguir o usuário remoto em vez disso." host: "Host" -selectSelf: "Escolher manualmente" +selectSelf: "Selecionar a mim" selectUser: "Selecionar usuário" recipient: "Destinatário" annotation: "Anotação" @@ -236,6 +239,8 @@ silencedInstances: "Instâncias silenciadas" silencedInstancesDescription: "Liste o nome de hospedagem dos servidores que você deseja silenciar, separados por linha. Todas as contas desses servidores serão silenciada e poderão enviar solicitações para seguir, mas não poderão mencionar usuários locais sem segui-los. Isso não afetará servidores bloqueados." mediaSilencedInstances: "Instâncias com mídia silenciadas" mediaSilencedInstancesDescription: "Liste o nome de hospedagem dos servidores cuja mídia você deseja silenciar, separados por linha. Todas as contas desses servidores serão consideradas sensíveis e não poderão utilizar emojis personalizados. Isso não afetará servidores bloqueados." +federationAllowedHosts: "Servidores com federação permitida" +federationAllowedHostsDescription: "Especifique o endereço dos servidores em que deseja permitir a federação separados por linha." muteAndBlock: "Silenciar e bloquear" mutedUsers: "Usuários silenciados" blockedUsers: "Usuários bloqueados" @@ -334,6 +339,7 @@ renameFolder: "Renomear Pasta" deleteFolder: "Excluir pasta" folder: "Pasta" addFile: "Adicionar arquivo" +showFile: "Mostrar arquivos" emptyDrive: "O drive está vazio" emptyFolder: "A pasta está vazia" unableToDelete: "Não é possível excluir" @@ -447,6 +453,7 @@ totpDescription: "Digite a senha de uso único informado pelo aplicativo autenti moderator: "Moderador" moderation: "Moderação" moderationNote: "Nota de moderação" +moderationNoteDescription: "Você pode preencher notas que serão compartilhadas apenas com moderadores." addModerationNote: "Adicionar nota de moderação" moderationLogs: "Logs de moderação" nUsersMentioned: "Postado por {n} pessoas" @@ -508,6 +515,10 @@ uiLanguage: "Idioma de exibição da interface " aboutX: "Sobre {x}" emojiStyle: "Estilo de emojis" native: "Nativo" +menuStyle: "Estilo do menu" +style: "Estilo" +drawer: "Gaveta" +popup: "Pop-up" showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela" showReactionsCount: "Ver o número de reações nas notas" noHistory: "Ainda não há histórico" @@ -575,6 +586,7 @@ masterVolume: "volume principal" notUseSound: "Desabilitar som" useSoundOnlyWhenActive: "Apenas reproduzir sons quando Misskey estiver aberto." details: "Detalhes" +renoteDetails: "Detalhes da repostagem" chooseEmoji: "Selecione um emoji" unableToProcess: "Não é possível concluir a operação" recentUsed: "Usado recentemente" @@ -590,6 +602,8 @@ ascendingOrder: "Ascendente" descendingOrder: "Descendente" scratchpad: "Bloco de rascunho" scratchpadDescription: "O Bloco de rascunho fornece um ambiente experimental para AiScript. Permite escrever, executar e verificar os resultados do código para interagir com o Misskey." +uiInspector: "Inspecionador de interface" +uiInspectorDescription: "Você pode ver a lista de servidores de componentes de interface na memória. Componentes da interface serão gerados pela função Ui:C:." output: "Resultado" script: "Script" disablePagesScript: "Desabilitar scripts nas páginas" @@ -670,7 +684,7 @@ 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" -hardWordMute: "SIlenciamento pesado de palavra" +hardWordMute: "Silenciar palavras (esconder posts)" 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" @@ -908,6 +922,7 @@ followersVisibility: "Visibilidade dos seguidores" continueThread: "Ver mais desta conversa" deleteAccountConfirm: "Deseja realmente excluir a conta?" incorrectPassword: "Senha inválida." +incorrectTotp: "A senha de uso único está incorreta ou expirou." voteConfirm: "Deseja confirmar o seu voto em \"{choice}\"?" hide: "Ocultar" useDrawerReactionPickerForMobile: "Mostrar em formato de gaveta" @@ -932,6 +947,9 @@ oneHour: "1 hora" oneDay: "1 dia" oneWeek: "1 semana" oneMonth: "1 mês" +threeMonths: "3 meses" +oneYear: "1 ano" +threeDays: "3 dias" reflectMayTakeTime: "As mudanças podem demorar a aparecer." failedToFetchAccountInformation: "Não foi possível obter informações da conta" rateLimitExceeded: "Taxa limite excedido" @@ -1072,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?" retryAllQueuesConfirmText: "Isso irá temporariamente aumentar a carga do servidor." enableChartsForRemoteUser: "Gerar gráficos estatísticos de usuários remotos" enableChartsForFederatedInstances: "Gerar gráficos estatísticos de instâncias remotas" +enableStatsForFederatedInstances: "Receber estatísticas de servidores remotos" showClipButtonInNoteFooter: "Adicionar \"Clip\" ao menu de ação de notas" reactionsDisplaySize: "Tamanho de exibição das reações" limitWidthOfReaction: "Limita o comprimento máximo de reações e as exibe em tamanho reduzido" @@ -1258,7 +1277,49 @@ confirmWhenRevealingSensitiveMedia: "Confirmar ao revelar mídia sensível" sensitiveMediaRevealConfirm: "Essa mídia pode ser sensível. Deseja revelá-la?" createdLists: "Listas criadas" createdAntennas: "Antenas criadas" +fromX: "De {x}" +genEmbedCode: "Gerar código de embed" +noteOfThisUser: "Notas por este usuário" clipNoteLimitExceeded: "Não é possível adicionar mais notas ao clipe." +performance: "Desempenho" +modified: "Modificado" +discard: "Descartar" +thereAreNChanges: "Há {n} mudança(s)" +signinWithPasskey: "Entrar com Passkey" +unknownWebAuthnKey: "Passkey desconhecida" +passkeyVerificationFailed: "A verificação com Passkey falhou." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "A verificação com Passkey teve êxito, mas a entrada sem senha está desabilitada." +messageToFollower: "Mensagem aos seguidores" +target: "Alvo" +testCaptchaWarning: "Essa função é utilizada apenas para testar CAPTCHA. Não a use num ambiente de produção." +prohibitedWordsForNameOfUser: "Palavras proibidas para nomes de usuário" +prohibitedWordsForNameOfUserDescription: "Se quaisquer palavras dessa lista forem incluídas no nome de usuário, seu uso será negado. Usuários com privilégios de moderador não serão afetados pela restrição." +yourNameContainsProhibitedWords: "O seu nome possui palavras proibidas" +yourNameContainsProhibitedWordsDescription: "Se você deseja utilizar esse nome, entre em contato com o administrador do servidor." +thisContentsAreMarkedAsSigninRequiredByAuthor: "O autor exige que você esteja cadastrado para ver" +lockdown: "Lockdown" +pleaseSelectAccount: "Selecione uma conta" +availableRoles: "Cargos disponíveis" +acknowledgeNotesAndEnable: "Ative após compreender as precauções." +_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." + requireSigninToViewContentsDescription2: "Conteúdo não será exibido nas prévias de URL (OGP), incorporado em outras páginas web ou em servidores que não têm suporte a citações." + requireSigninToViewContentsDescription3: "Essas restrições podem não ser aplicadas a conteúdo federado de outros servidores." + makeNotesFollowersOnlyBefore: "Tornar notas passadas visíveis apenas para seguidores." + makeNotesFollowersOnlyBeforeDescription: "Com essa função ativada, apenas seguidores podem ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido." + 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." + notesHavePassedSpecifiedPeriod: "Notas que duraram um tempo específico." + notesOlderThanSpecifiedDateAndTime: "Notas antes do tempo específico." +_abuseUserReport: + forward: "Encaminhar" + forwardDescription: "Encaminhar a denúncia ao servidor remoto como uma conta anônima do sistema." + resolve: "Resolver" + accept: "Aceitar" + reject: "Rejeitar" + resolveTutorial: "Se a denúncia for legítima em conteúdo, selecione \"Aceitar\" para marcar o caso como resolvido afirmativamente.\nSe a denúncia for ilegítima em conteúdo, selecione \"Rejeitar\" para marcar o caso como resolvido negativamente." _delivery: status: "Estado de entrega" stop: "Suspenso" @@ -1393,8 +1454,12 @@ _serverSettings: fanoutTimelineDescription: "Melhora significativamente a performance do retorno da linha do tempo e reduz o impacto no banco de dados quando habilitado. Em contrapartida, o uso de memória do Redis aumentará. Considere desabilitar em casos de baixa disponibilidade de memória ou instabilidade do servidor." 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." 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." _accountMigration: moveFrom: "Migrar outra conta para essa" moveFromSub: "Criar um 'alias' a outra conta" @@ -1726,6 +1791,11 @@ _role: canSearchNotes: "Permitir a busca de notas" canUseTranslator: "Uso do tradutor" avatarDecorationLimit: "Número máximo de decorações de avatar que podem ser aplicadas" + canImportAntennas: "Permitir importação de antenas" + canImportBlocking: "Permitir importação de bloqueios" + canImportFollowing: "Permitir importação de usuários seguidos" + canImportMuting: "Permitir importação de silenciamentos" + canImportUserLists: "Permitir importação de listas" _condition: roleAssignedTo: "Atribuído a cargos manuais" isLocal: "Usuário local" @@ -2109,8 +2179,11 @@ _auth: permissionAsk: "O aplicativo solicita as seguintes permissões" pleaseGoBack: "Por favor, volte ao aplicativo" callback: "Retornando ao aplicativo" + accepted: "Acesso permitido" denied: "Acesso negado" + scopeUser: "Operar como o usuário a seguir" pleaseLogin: "Por favor, entre para autorizar aplicativos." + byClickingYouWillBeRedirectedToThisUrl: "Quando o acesso for permitido, você será redirecionado para o seguinte endereço" _antennaSources: all: "Todas as notas" homeTimeline: "Notas de usuários seguidos" @@ -2219,6 +2292,9 @@ _profile: changeBanner: "Mudar banner" verifiedLinkDescription: "Ao inserir um URL que contém um link para essa conta, um ícone de verificação será exibido ao lado do campo" avatarDecorationMax: "Você pode adicionar até {max} decorações." + followedMessage: "Mensagem exibida quando alguém segue você" + followedMessageDescription: "Você pode definir uma curta mensagem que será exibida aos usuários que seguirem você." + followedMessageDescriptionForLockedAccount: "Se você aceita pedidos de seguidor manualmente, isso será exibido quando você aceitá-los." _exportOrImport: allNotes: "Todas as notas" favoritedNotes: "Notas nos favoritos" @@ -2357,6 +2433,8 @@ _notification: renotedBySomeUsers: "{n} usuários repostaram a nota" followedBySomeUsers: "{n} usuários te seguiram" flushNotification: "Limpar notificações" + exportOfXCompleted: "Exportação de {x} foi concluída" + login: "Alguém entrou na conta" _types: all: "Todas" note: "Novas notas" @@ -2371,7 +2449,9 @@ _notification: followRequestAccepted: "Aceitou pedidos de seguidor" roleAssigned: "Cargo dado" achievementEarned: "Conquista desbloqueada" + exportCompleted: "A exportação foi concluída" login: "Iniciar sessão" + test: "Notificação teste" app: "Notificações de aplicativos conectados" _actions: followBack: "te seguiu de volta" @@ -2437,7 +2517,10 @@ _webhookSettings: abuseReport: "Quando receber um relatório de abuso" abuseReportResolved: "Quando relatórios de abuso forem resolvidos " userCreated: "Quando um usuário é criado" + inactiveModeratorsWarning: "Quando moderadores estiverem inativos por um tempo" + inactiveModeratorsInvitationOnlyChanged: "Quando um moderador está inativo por um tempo e os cadastros passam a exigir convites" deleteConfirm: "Você tem certeza de que deseja excluir o Webhook?" + testRemarks: "Clique no botão à direita do interruptor para enviar um Webhook de teste com dados fictícios." _abuseReport: _notificationRecipient: createRecipient: "Adicionar destinatário para relatórios de abuso" @@ -2481,6 +2564,8 @@ _moderationLogTypes: markSensitiveDriveFile: "Arquivo marcado como sensível" unmarkSensitiveDriveFile: "Arquivo desmarcado como sensível" resolveAbuseReport: "Relatório resolvido" + forwardAbuseReport: "Denúncia encaminhada" + updateAbuseReportNote: "Nota de moderação da denúncia atualizada" createInvitation: "Convite gerado" createAd: "Propaganda criada" deleteAd: "Propaganda excluída" @@ -2636,3 +2721,44 @@ _contextMenu: app: "Aplicativo" appWithShift: "Aplicativo com a tecla shift" native: "Nativo" +_embedCodeGen: + title: "Personalizar código do embed" + header: "Exibir cabeçalho" + autoload: "Carregar mais automaticamente (obsoleto)" + maxHeight: "Altura máxima" + maxHeightDescription: "Colocar em 0 desabilita a altura máxima. Especifique um valor para prevenir uma expansão vertical contínua." + maxHeightWarn: "O limite de altura máxima está desabilitado (0). Se isso não for intencional, insira um valor para a altura máxima." + previewIsNotActual: "A exibição difere do embed original porque ela excede o tamanho da tela de prévia." + rounded: "Tornar arredondado" + border: "Adicionar uma borda ao quadro externo" + applyToPreview: "Aplicar para a prévia" + generateCode: "Gerar código de embed" + codeGenerated: "O código foi gerado" + codeGeneratedDescription: "Coloque o código no seu website para incorporar o conteúdo." +_selfXssPrevention: + warning: "AVISO" + title: "\"Cole algo nessa tela\" é uma fraude" + description1: "Se você colar algo aqui, um usuário malicioso pode sabotar a sua conta ou roubar informações pessoais." + description2: "Se você não entender exatamente o que está colando, %cpare agora e feche essa janela." + description3: "Para mais informação, clique no link. {link}" +_followRequest: + recieved: "Aplicação recebida" + sent: "Aplicação enviada" +_remoteLookupErrors: + _federationNotAllowed: + title: "Não foi possível se comunicar com o servidor" + description: "Comunicação com esse servidor pode ter sido desabilitada ou o servidor pode ter sido bloqueado.\nPor favor, entre em contato com o administrador do servidor." + _uriInvalid: + title: "Endereço inválido" + description: "Há um problema com o endereço inserido. Por favor, confira se você não inseriu caracteres inválidos." + _requestFailed: + title: "Solicitação falhou" + description: "Comunicação com esse servidor falhou. O servidor pode estar inativo. Além disso, confira se você não inseriu um endereço inválido ou inexistente." + _responseInvalid: + title: "Resposta inválida" + description: "Foi possível comunicar com o servidor, porém os dados obtidos foram incorretos." + _responseInvalidIdHostNotMatch: + description: "O domínio do endereço inserido difere do domínio do endereço final. Se você estiver pesquisando por um servidor de terceiros, tente buscar novamente com um endereço que pode ser obtido através do servidor original." + _noSuchObject: + title: "Não encontrado" + description: "O recurso solicitado não foi encontrado, confira o endereço." diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 71dc1dc94cea..07f4c98d969b 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -733,3 +733,6 @@ _moderationLogTypes: resetPassword: "Resetează parola" _reversi: total: "Total" +_remoteLookupErrors: + _noSuchObject: + title: "Nu a fost găsit" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 537e99036c07..bc1b12895ca9 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -18,7 +18,7 @@ gotIt: "Ясно!" cancel: "Отмена" noThankYou: "Нет, спасибо" enterUsername: "Введите имя пользователя" -renotedBy: "{user} репостнул(а)" +renotedBy: "{user} делает репост" noNotes: "Нет ни одной заметки" noNotifications: "Нет уведомлений" instance: "Экземпляр" @@ -1063,7 +1063,7 @@ hiddenTags: "Скрытые хештеги" notesSearchNotAvailable: "Поиск заметок недоступен" license: "Лицензия" unfavoriteConfirm: "Удалить избранное?" -myClips: "Мои клипы" +myClips: "Мои подборки" drivecleaner: "Очиститель дисков" retryAllQueuesNow: "Повторить все очереди сейчас" retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" @@ -1105,16 +1105,18 @@ preservedUsernames: "Зарезервированные имена пользо preservedUsernamesDescription: "Перечислите зарезервированные имена пользователей, отделяя их строками. Они станут недоступны при создании учётной записи. Это ограничение не применяется при создании учётной записи администраторами. Также, уже существующие учётные записи останутся без изменений." createNoteFromTheFile: "Создать заметку из этого файла" archive: "Архив" +unarchive: "Разархивировать" channelArchiveConfirmTitle: "Переместить {name} в архив?" channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." thisChannelArchived: "Этот канал находится в архиве." displayOfNote: "Отображение заметок" initialAccountSetting: "Настройка профиля" -youFollowing: "Подписки" +youFollowing: "Вы подписаны" preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)" preventAiLearningDescription: "Запросить краулеров не использовать опубликованный текст или изображения и т.д. для машинного обучения (Прогнозирующий / Генеративный ИИ) датасетов. Это достигается путём добавления \"noai\" HTTP-заголовка в ответ на соответствующий контент. Полного предотвращения через этот заголовок не избежать, так как он может быть просто проигнорирован." options: "Настройки ролей" specifyUser: "Указанный пользователь" +lookupConfirm: "Хотите узнать?" openTagPageConfirm: "Открыть страницу этого хештега?" specifyHost: "Указать сайт" failedToPreviewUrl: "Предварительный просмотр недоступен" @@ -1178,6 +1180,7 @@ keepOriginalFilename: "Сохранять исходное имя файла" keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке." alwaysConfirmFollow: "Всегда подтверждать подписку" inquiry: "Связаться" +messageToFollower: "Сообщение подписчикам" _delivery: stop: "Заморожено" _type: @@ -1504,6 +1507,7 @@ _role: rateLimitFactor: "Ограничение активности" descriptionOfRateLimitFactor: "Меньшее значение — слабые ограничения, большее — сильные" canHideAds: "Может скрыть рекламу" + canImportFollowing: "Можно импортировать подписчиков" _condition: isLocal: "Местный" isRemote: "Неместный" @@ -2143,3 +2147,6 @@ _hemisphere: caption: "Используется для некоторых настроек клиента для определения сезона." _reversi: total: "Всего" +_remoteLookupErrors: + _noSuchObject: + title: "Не найдено" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index f3f43ee6a6b6..715ff4c847ce 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -1449,3 +1449,6 @@ _moderationLogTypes: resetPassword: "Resetovať heslo" _reversi: total: "Celkom" +_remoteLookupErrors: + _noSuchObject: + title: "Nenájdené" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index c725282d5073..8e68d6cf49ec 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -2709,3 +2709,6 @@ _embedCodeGen: generateCode: "สร้างโค้ดสำหรับการฝัง" codeGenerated: "รหัสถูกสร้างขึ้นแล้ว" codeGeneratedDescription: "นำโค้ดที่สร้างแล้วไปวางในเว็บไซต์ของคุณเพื่อฝังเนื้อหา" +_remoteLookupErrors: + _noSuchObject: + title: "ไม่พบหน้าที่ต้องการ" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 69892fedc80f..2c63f15aa20d 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -8,6 +8,7 @@ search: "Arama" notifications: "Bildirim" username: "Kullanıcı Adı" password: "Şifre" +initialPasswordForSetup: "" forgotPassword: "şifremi unuttum" fetchingAsApObject: "從聯邦宇宙取得中..." ok: "TAMAM" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 1b2185465058..6e3e0bb9daa9 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -1624,3 +1624,6 @@ _moderationLogTypes: resetPassword: "Скинути пароль" _reversi: total: "Всього" +_remoteLookupErrors: + _noSuchObject: + title: "Не знайдено" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 051a4ae6c5f5..2116d2b86fd9 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -1094,3 +1094,6 @@ _moderationLogTypes: resetPassword: "Parolni tiklash" _reversi: total: "Jami" +_remoteLookupErrors: + _noSuchObject: + title: "Topilmadi" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 24faa4b94c76..cded29fdba9b 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1930,3 +1930,6 @@ _moderationLogTypes: createInvitation: "Tạo lời mời" _reversi: total: "Tổng cộng" +_remoteLookupErrors: + _noSuchObject: + title: "Không tìm thấy" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index e6232070d76b..cb691f3b8798 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -5,6 +5,7 @@ introMisskey: "欢迎!Misskey是一个开源的、去中心化的“微博客 poweredByMisskeyDescription: "{name} 是开源平台 Misskey 的服务器之一。" monthAndDay: "{month}月 {day}日" search: "搜索" +reset: "重置" notifications: "通知" username: "用户名" password: "密码" @@ -48,6 +49,7 @@ pin: "置顶" unpin: "取消置顶" copyContent: "复制内容" copyLink: "复制链接" +copyRemoteLink: "复制远程连接" copyLinkRenote: "复制转帖链接" delete: "删除" deleteAndEdit: "删除并编辑" @@ -142,15 +144,15 @@ markAsSensitive: "标记为敏感内容" unmarkAsSensitive: "取消标记为敏感内容" enterFileName: "输入文件名" mute: "屏蔽" -unmute: "解除静音" +unmute: "取消隐藏" renoteMute: "隐藏转帖" renoteUnmute: "解除隐藏转帖" -block: "拉黑" -unblock: "取消拉黑" +block: "屏蔽" +unblock: "取消屏蔽" suspend: "冻结" unsuspend: "解除冻结" -blockConfirm: "确定要拉黑吗?" -unblockConfirm: "确定要解除拉黑吗?" +blockConfirm: "确定要屏蔽吗?" +unblockConfirm: "确定要取消屏蔽吗?" suspendConfirm: "要冻结吗?" unsuspendConfirm: "要解除冻结吗?" selectList: "选择列表" @@ -195,7 +197,7 @@ setWallpaper: "设置壁纸" removeWallpaper: "移除壁纸" searchWith: "搜索:{q}" youHaveNoLists: "列表为空" -followConfirm: "你确定要关注 {name} 吗?" +followConfirm: "确定要关注 {name} 吗?" proxyAccount: "代理账户" proxyAccountDescription: "代理账户是在某些情况下替代用户进行远程关注用的账户。 例如说,当用户将一位远程用户放入一个列表中时,如果本地服务器上没有任何人关注这位远程用户,则这位远程用户的账户活动将不会被送到本地服务器上。作为替代,此时将使用代理账户进行关注。" host: "主机名" @@ -229,10 +231,10 @@ disk: "存储" instanceInfo: "服务器信息" statistics: "统计" clearQueue: "清除队列" -clearQueueConfirmTitle: "确定清除队列?" +clearQueueConfirmTitle: "确定要清除队列吗?" clearQueueConfirmText: "未送达的帖子将不会被投递。 通常无需执行此操作。" clearCachedFiles: "清除缓存" -clearCachedFilesConfirm: "确定要清除所有缓存的远程文件?" +clearCachedFilesConfirm: "确定要清除所有缓存的远程文件吗?" blockedInstances: "被屏蔽的服务器" blockedInstancesDescription: "设定要屏蔽的服务器,以换行分隔。被屏蔽的服务器将无法与本服务器进行交换通讯。子域名也同样会被屏蔽。" silencedInstances: "被静音的服务器" @@ -246,7 +248,7 @@ mutedUsers: "已隐藏用户" blockedUsers: "已屏蔽的用户" noUsers: "无用户" editProfile: "编辑资料" -noteDeleteConfirm: "要删除该帖子吗?" +noteDeleteConfirm: "确定要删除该帖子吗?" pinLimitExceeded: "无法置顶更多了" intro: "Misskey 的部署结束啦!创建管理员账号吧!" done: "完成" @@ -257,7 +259,7 @@ defaultValueIs: "默认值: {value}" noCustomEmojis: "没有自定义表情符号" noJobs: "没有任务" federating: "联合中" -blocked: "已拉黑" +blocked: "已屏蔽" suspended: "停止投递" all: "全部" subscribing: "已订阅" @@ -566,7 +568,7 @@ objectStorageRegionDesc: "指定一个可用区,例如“xx-east-1”。 如 objectStorageUseSSL: "使用 SSL" objectStorageUseSSLDesc: "如果不使用 https 进行 API 连接,请关闭。" objectStorageUseProxy: "使用代理" -objectStorageUseProxyDesc: "如果您不使用代理进行 API 连接,请将其关闭。" +objectStorageUseProxyDesc: "如果不使用代理进行 API 连接,请关闭。" objectStorageSetPublicRead: "上传时设置为 public-read" s3ForcePathStyleDesc: "启用 s3ForcePathStyle 会强制将存储桶名称指定为 URL 中路径的一部分,而不是主机名。使用自托管 Minio 等时可能需要启用。" serverLogs: "服务器日志" @@ -683,12 +685,16 @@ emptyToDisableSmtpAuth: "用户名和密码留空可以禁用 SMTP 验证" smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS" smtpSecureInfo: "使用 STARTTLS 时关闭。" testEmail: "邮件发送测试" -wordMute: "隐藏文字" -hardWordMute: "屏蔽关键词" +wordMute: "隐藏关键词" +wordMuteDescription: "折叠包含指定关键词的帖子。被折叠的帖子可单击展开。" +hardWordMute: "隐藏硬关键词" +showMutedWord: "显示已隐藏的关键词" +hardWordMuteDescription: "隐藏包含指定关键词的帖子。与隐藏关键词不同,帖子将完全不会显示。" regexpError: "正则表达式错误" -regexpErrorDescription: "{tab} 屏蔽文字的第 {line} 行的正则表达式有错误:" +regexpErrorDescription: "{tab} 隐藏文字的第 {line} 行的正则表达式有错误:" instanceMute: "已隐藏的服务器" userSaysSomething: "{name} 说了什么,但是被屏蔽词过滤了" +userSaysSomethingAbout: "{name} 说了关于「{word}」的什么" makeActive: "启用" display: "显示" copy: "复制" @@ -759,7 +765,7 @@ driveFilesCount: "网盘的文件数" driveUsage: "网盘的空间用量" noCrawle: "要求搜索引擎不索引该用户" noCrawleDescription: "要求搜索引擎不要收录(索引)您的用户页面,帖子,页面等。" -lockedAccountInfo: "即使启用该功能,只要您不将帖子可见范围设置为“仅关注者”,任何人都还是可以看到您的帖子。" +lockedAccountInfo: "即使启用该功能,只要帖子可见范围不是「仅关注者」,任何人都可以看到您的帖子。" alwaysMarkSensitive: "默认将媒体文件标记为敏感内容" loadRawImages: "添加附件图像的缩略图时使用原始图像质量" disableShowingAnimatedImages: "不播放动画" @@ -846,7 +852,7 @@ active: "活动" offline: "离线" notRecommended: "不推荐" botProtection: "Bot防御" -instanceBlocking: "被阻拦的服务器" +instanceBlocking: "屏蔽/静音的服务器" selectAccount: "选择账户" switchAccount: "切换账户" enabled: "已启用" @@ -1301,6 +1307,8 @@ lockdown: "锁定" pleaseSelectAccount: "请选择帐户" availableRoles: "可用角色" acknowledgeNotesAndEnable: "理解注意事项后再开启。" +federationSpecified: "此服务器已开启联合白名单。只能与管理员指定的服务器通信。" +federationDisabled: "此服务器已禁用联合。无法与其它服务器上的用户通信。" _accountSettings: requireSigninToViewContents: "需要登录才能显示内容" requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。" @@ -1319,7 +1327,7 @@ _abuseUserReport: resolve: "解决" accept: "确认" reject: "拒绝" - resolveTutorial: "如果举报内容有理且已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果举报内容站不住脚,选择「拒绝」将案件以否定的态度标记为已解决。" + resolveTutorial: "如果认可举报并已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果不认可举报,选择「拒绝」将案件以否定的态度标记为已解决。" _delivery: status: "投递状态" stop: "停止投递" @@ -1468,7 +1476,7 @@ _accountMigration: moveTo: "把这个账户迁移到新的账户" moveToLabel: "迁移后的账户" moveCannotBeUndone: "一旦迁移账户,就无法撤销。" - moveAccountDescription: "\n迁移到新帐户。\n ・现有的关注者自动关注新帐户\n ・此帐户的所有关注者都将被删除\n ・您将无法再使用此帐户发帖。\n关注者迁移是自动的,但关注中迁移必须手动完成。请在迁移前在此帐户上导出关注列表,并在迁移后立即在目标帐户上执行导入。\n屏蔽列表也是如此,因此您必须手动迁移它。\n(此描述适用于该服务器(Misskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为可能有所不同。)" + moveAccountDescription: "\n迁移到新帐户。\n ・现有的关注者自动关注新帐户\n ・此帐户的所有关注者都将被删除\n ・您将无法再使用此帐户发帖。\n关注者迁移是自动的,但关注中迁移必须手动完成。请在迁移前在此帐户上导出关注列表,并在迁移后立即在目标帐户上执行导入。\n列表、隐藏、屏蔽也是如此,因此您必须手动迁移它。\n(此描述适用于该服务器(Misskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为可能有所不同。)" moveAccountHowTo: "要进行账户迁移,请现在目标账户中为此账户建立一个别名。\n建立别名后,请像这样输入目标账户:@username@server.example.com" startMigration: "迁移" migrationConfirm: "确定要把此账户迁移到 {account} 吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。" @@ -1688,7 +1696,7 @@ _achievements: title: "超高校级的幸运" description: "每 10 秒有 0.005% 的概率自动获得" _setNameToSyuilo: - title: "像神一样呐" + title: "上帝情结" description: "将名称设定为 syuilo" _passedSinceAccountCreated1: title: "一周年" @@ -1794,7 +1802,7 @@ _role: canImportAntennas: "允许导入天线" canImportBlocking: "允许导入屏蔽列表" canImportFollowing: "允许导入关注列表" - canImportMuting: "允许导入屏蔽列表" + canImportMuting: "允许导入隐藏列表" canImportUserLists: "允许导入用户列表" _condition: roleAssignedTo: "已分配给手动角色" @@ -1948,7 +1956,7 @@ _wordMute: _instanceMute: instanceMuteDescription: "隐藏服务器中的所有帖子和转帖,包括这些服务器上的用户回复。" instanceMuteDescription2: "一行一个" - title: "隐藏服务器已设置的帖子。" + title: "下面实例中的帖子将被隐藏。" heading: "已隐藏的服务器" _theme: explore: "寻找主题" @@ -2069,12 +2077,12 @@ _2fa: step4: "从现在开始,任何登录操作都将要求您提供动态口令。" securityKeyNotSupported: "您的浏览器不支持安全密钥。" registerTOTPBeforeKey: "要注册安全密钥或 Passkey,请先设置验证器。" - securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。" + securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 以及 Passkey 等。" registerSecurityKey: "注册安全密钥或 Passkey" securityKeyName: "输入密钥名称" tapSecurityKey: "请按照浏览器说明操作来注册安全密钥或 Passkey。" removeKey: "删除安全密钥" - removeKeyConfirm: "您确定要删除 {name} 吗?" + removeKeyConfirm: "确定要删除 {name} 吗?" whyTOTPOnlyRenew: "当注册了安全密钥时,无法取消使用验证器。" renewTOTP: "重置验证器" renewTOTPConfirm: "当前验证器的验证码及备用代码已失效" @@ -2282,7 +2290,7 @@ _profile: name: "昵称" username: "用户名" description: "个人简介" - youCanIncludeHashtags: "你可以在个人简介中包含一些#标签。" + youCanIncludeHashtags: "可以在个人简介中包含 #标签。" metadata: "附加信息" metadataEdit: "附加信息编辑" metadataDescription: "最多可以在个人资料中以表格形式显示四条其他信息。" @@ -2386,7 +2394,7 @@ _pages: fontSansSerif: "无衬线字体" eyeCatchingImageSet: "设置封面图片" eyeCatchingImageRemove: "删除封面图片" - chooseBlock: "添加块" + chooseBlock: "添加内容块" enterSectionTitle: "输入会话标题" selectType: "选择类型" contentBlocks: "内容" @@ -2398,8 +2406,8 @@ _pages: section: "章节" image: "图片" button: "按钮" - dynamic: "动态区块" - dynamicDescription: "这个区块已经废弃。以后请使用{play}。" + dynamic: "动态内容块" + dynamicDescription: "这个内容块已经废弃。以后请使用{play}。" note: "嵌入的帖子" _note: id: "帖子 ID" @@ -2433,7 +2441,7 @@ _notification: renotedBySomeUsers: "{n} 人转发了" followedBySomeUsers: "被 {n} 人关注" flushNotification: "重置通知历史" - exportOfXCompleted: "已完成 {x} 个导出" + exportOfXCompleted: "已完成 {x} 的导出" login: "有新的登录" _types: all: "全部" @@ -2721,6 +2729,66 @@ _contextMenu: app: "应用" appWithShift: "Shift 键应用" native: "浏览器的用户界面" +_gridComponent: + _error: + requiredValue: "此值为必填项" + columnTypeNotSupport: "正则表达式验证仅支持 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: "此页面上的表情符号已更改。\n若不保存就离开此页,此页面上所有的更改都将丢失。" + dialogSelectRoleTitle: "按角色搜索表情符号" + _register: + uploadSettingTitle: "上传设置" + uploadSettingDescription: "可以在此页面设置上传表情符号时的行为。" + directoryToCategoryLabel: "目录名请输入「category」" + directoryToCategoryCaption: "拖放目录时,目录名请输入「category」" + emojiInputAreaCaption: "请使用其中一种方法选择要注册的表情符号。" + emojiInputAreaList1: "在此区域内拖放图像文件或者目录" + emojiInputAreaList2: "单击此链接以从电脑中选择" + emojiInputAreaList3: "单击此链接以从网盘中选择" + confirmRegisterEmojisDescription: "要将列表内显示的表情符号替换为新的自定义表情符号吗?(为降低服务器负载,一次操作最多只能注册 {count} 个表情符号)" + confirmClearEmojisDescription: "要放弃编辑并将列表内表示的表情符号清空吗?" + confirmUploadEmojisDescription: "要将拖放的 {count} 个文件上传到网盘上吗?" _embedCodeGen: title: "自定义嵌入代码" header: "显示标题" @@ -2744,3 +2812,34 @@ _selfXssPrevention: _followRequest: recieved: "已收到申请" sent: "已发送申请" +_remoteLookupErrors: + _federationNotAllowed: + title: "无法与此服务器通信" + description: "与此服务器的通信可能被禁用,又或者是屏蔽了此服务器或被此服务器屏蔽了。\n请联系服务器的管理者。" + _uriInvalid: + title: "URI 有误" + description: "输入的 URI 有问题。请确认是否输入了 URI 中无法使用的字符。" + _requestFailed: + title: "请求失败" + description: "与该服务器的通信失败。对面服务器可能不可用。另外,请确认是否输入了无效或不存在的 URI。" + _responseInvalid: + title: "响应无效" + description: "成功与此服务器通信,但返回的数据无效。" + _responseInvalidIdHostNotMatch: + description: "输入 URI 的域名和最终取得的 URI 的域名不同。如果是通过第三方服务器获取远程内容,请使用可以从原始服务器获取内容的 URI 再试一次。" + _noSuchObject: + title: "未找到" + description: "未找到请求的资源。请再次检查 URI。" +_captcha: + verify: "请通过 CAPTCHA 验证" + testSiteKeyMessage: "输入测试用的网站密钥及私密密钥后可以生成预览并检查,\n详情请看以下页面。" + _error: + _requestFailed: + title: "请求 CAPTCHA 失败" + text: "请稍后再试,又或者再检查一次设置。" + _verificationFailed: + title: "验证 CAPTCHA 失败" + text: "请再次确认设置是否正确。" + _unknown: + title: "CAPTCHA 错误" + text: "发生意外错误。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index d4ffb28c7629..159ede135626 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -5,6 +5,7 @@ introMisskey: "歡迎!Misskey 是一個開放原始碼且去中心化的社群 poweredByMisskeyDescription: "{name}是開放原始碼平臺 Misskey 的伺服器之一。" monthAndDay: "{month} 月 {day} 日" search: "搜尋" +reset: "重設" notifications: "通知" username: "使用者名稱" password: "密碼" @@ -48,6 +49,7 @@ pin: "置頂" unpin: "取消置頂" copyContent: "複製內容" copyLink: "複製連結" +copyRemoteLink: "複製遠端的連結" copyLinkRenote: "複製轉發的連結" delete: "刪除" deleteAndEdit: "刪除並編輯" @@ -230,7 +232,7 @@ instanceInfo: "伺服器資訊" statistics: "統計" clearQueue: "清除佇列" clearQueueConfirmTitle: "確定要清除佇列嗎?" -clearQueueConfirmText: "未發佈的貼文將不會發佈。您通常不需要確認。" +clearQueueConfirmText: "未成功發佈的貼文將不會再嘗試發佈。通常不需要進行這項操作。" clearCachedFiles: "清除快取資料" clearCachedFilesConfirm: "確定要清除所有遠端暫存資料嗎?" blockedInstances: "已封鎖的伺服器" @@ -291,8 +293,8 @@ messaging: "聊天" upload: "上傳" keepOriginalUploading: "保留原圖" keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成適用於網路傳送的版本。" -fromDrive: "從雲端空間" -fromUrl: "從 URL" +fromDrive: "從雲端空間中選擇" +fromUrl: "從 URL 上傳" uploadFromUrl: "從網址上傳" uploadFromUrlDescription: "您要上傳的檔案網址" uploadFromUrlRequested: "已請求上傳" @@ -324,7 +326,7 @@ light: "淺色" dark: "深色" lightThemes: "淺色佈景主題" darkThemes: "深色佈景主題" -syncDeviceDarkMode: "與設備的深色模式同步" +syncDeviceDarkMode: "與裝置的深色模式同步" drive: "雲端硬碟" fileName: "檔案名稱" selectFile: "選擇檔案" @@ -684,11 +686,15 @@ smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS" smtpSecureInfo: "使用 STARTTLS 時關閉。" testEmail: "測試郵件發送" wordMute: "被靜音的文字" +wordMuteDescription: "將包含指定語句的貼文最小化。 點擊最小化的貼文即可顯示。" hardWordMute: "硬文字靜音" +showMutedWord: "顯示靜音字" +hardWordMuteDescription: "隱藏含有指定語句的貼文。 與詞彙靜音不同的是,貼文將完全隱藏不見。" regexpError: "正規表達式錯誤" regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:" instanceMute: "被靜音的實例" userSaysSomething: "{name}說了什麼" +userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話" makeActive: "啟用" display: "檢視" copy: "複製" @@ -764,7 +770,7 @@ alwaysMarkSensitive: "預設標記檔案為敏感內容" loadRawImages: "以原始圖檔顯示附件圖檔的縮圖" disableShowingAnimatedImages: "不播放動態圖檔" highlightSensitiveMedia: "強調敏感標記" -verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。" +verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的連結以完成驗證。" notSet: "未設定" emailVerified: "已成功驗證您的電子郵件地址" noteFavoritesCount: "我的最愛貼文的數目" @@ -821,7 +827,7 @@ apply: "套用" receiveAnnouncementFromInstance: "接收來自伺服器的通知" emailNotification: "郵件通知" publish: "發布" -inChannelSearch: "頻道内搜尋" +inChannelSearch: "頻道內搜尋" useReactionPickerForContextMenu: "點擊右鍵開啟反應選擇器" typingUsers: "{users}輸入中" jumpToSpecifiedDate: "跳轉到特定日期" @@ -925,7 +931,7 @@ incorrectPassword: "密碼錯誤。" incorrectTotp: "一次性密碼錯誤,或者已過期。" voteConfirm: "確定投給「{choice}」?" hide: "隱藏" -useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示" +useDrawerReactionPickerForMobile: "在行動裝置上使用抽屜顯示" welcomeBackWithName: "歡迎回來,{name}" clickToFinishEmailVerification: "點擊 [{ok}] 完成電子郵件地址認證。" overridedDeviceKind: "裝置類型" @@ -1006,7 +1012,7 @@ unsubscribePushNotification: "停用推播通知" pushNotificationAlreadySubscribed: "推播通知啟用中" pushNotificationNotSupported: "瀏覽器或伺服器不支援推播通知" sendPushNotificationReadMessage: "如果已閱讀通知與訊息,就刪除推播通知" -sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」通知將立刻顯示。可能會更消耗裝置電池。" +sendPushNotificationReadMessageCaption: "可能會導致裝置的電池消耗量增加。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "復原" @@ -1175,20 +1181,20 @@ used: "已使用" expired: "過期" doYouAgree: "你同意嗎?" beSureToReadThisAsItIsImportant: "重要,請務必閱讀。" -iHaveReadXCarefullyAndAgree: "我已仔細閱讀並同意「{x}」的内容。" +iHaveReadXCarefullyAndAgree: "我已仔細閱讀並同意「{x}」的內容。" dialog: "對話方塊" icon: "圖示" forYou: "給您" currentAnnouncements: "最新公告" pastAnnouncements: "歷史公告" youHaveUnreadAnnouncements: "有未讀的公告。" -useSecurityKey: "請按照瀏覽器或設備上的說明使用安全金鑰或 Passkey。" +useSecurityKey: "請按照瀏覽器或裝置上的說明來使用安全金鑰或 Passkey。" replies: "回覆" renotes: "轉發" loadReplies: "閱覽回覆" loadConversation: "閱覽對話" pinnedList: "已置頂的清單" -keepScreenOn: "保持設備螢幕開啟" +keepScreenOn: "保持裝置螢幕開啟" verifiedLink: "已驗證連結" notifyNotes: "開啟貼文通知" unnotifyNotes: "關閉貼文通知" @@ -1243,7 +1249,7 @@ overwriteContentConfirm: "確定要覆蓋目前的內容嗎?" seasonalScreenEffect: "隨季節變換畫面的呈現" decorate: "設置頭像裝飾" addMfmFunction: "插入 MFM 功能語法" -enableQuickAddMfmFunction: "顯示高級 MFM 選擇器" +enableQuickAddMfmFunction: "顯示進階 MFM 選擇器" bubbleGame: "氣泡遊戲" sfx: "音效" soundWillBePlayed: "將播放音效" @@ -1270,7 +1276,7 @@ useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊" keepOriginalFilename: "保留原始檔名" keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。" noDescription: "沒有說明文字" -alwaysConfirmFollow: "跟隨時總是確認" +alwaysConfirmFollow: "追隨時總是確認" inquiry: "聯絡我們" tryAgain: "請再試一次。" confirmWhenRevealingSensitiveMedia: "要顯示敏感媒體時需確認" @@ -1301,6 +1307,8 @@ lockdown: "鎖定" pleaseSelectAccount: "請選擇帳戶" availableRoles: "可用角色" acknowledgeNotesAndEnable: "了解注意事項後再開啟。" +federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。" +federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。" _accountSettings: requireSigninToViewContents: "須登入以顯示內容" requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。" @@ -1366,7 +1374,7 @@ _initialAccountSetting: theseSettingsCanEditLater: "這裡的設定可以在之後變更。" youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。" - pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。" + pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自{name}的通知了。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" youCanContinueTutorial: "您可以繼續學習如何使用{name}(Misskey),也可以就此打住,立即開始使用。" @@ -1386,12 +1394,12 @@ _initialTutorial: description: "在Misskey上發布的內容稱為「貼文」。貼文在時間軸上按時間順序排列,並即時更新。" reply: "您可以回覆貼文,並像討論串一樣繼續對話。" renote: "您可以將此貼文分享到自己的時間軸。您也可以在引用時添加文字。" - reaction: "您可以添加反應。詳細資訊將在下一頁進行說明。" + reaction: "您可以加入反應。詳細資訊將在下一頁進行說明。" menu: "可執行各種操作,如查看貼文詳細資訊和複製連結。" _reaction: title: "什麼是反應?" - description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。" - letsTryReacting: "可以透過點擊貼文上的「+」按鈕來添加反應。請嘗試在此範例貼文添加反應!" + description: "您可以在貼文中加上「反應」。有些用「最愛/大心」無法傳達的感想,可以用反應輕鬆地表達出來。" + letsTryReacting: "按一下貼文上的「+」按鈕即可加入反應。試著對此範例貼文加上反應!" reactToContinue: "添加反應以繼續教學課程。" reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。" reactDone: "按下「-」按鈕可以取消反應。" @@ -1473,7 +1481,7 @@ _accountMigration: startMigration: "遷移" migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。" movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。" - postMigrationNote: "取消追蹤此帳戶將在遷移操作後 24 小時執行。\n 此帳戶有 0 個關注者/關注者。 您的關注者仍然可以看到此帳戶的關注者帖子,因為您不會被取消關注。" + postMigrationNote: "將在完成遷移後的 24 小時取消追隨所有帳號。\n此帳戶的追隨中/追隨者人數將歸零。由於不會解除粉絲對您的追隨,因此他們仍然可以繼續閱覽此帳戶僅對追隨者公開的貼文。" movedTo: "要遷移到的帳戶:" _achievements: earnedAt: "獲得日期" @@ -1793,7 +1801,7 @@ _role: avatarDecorationLimit: "頭像裝飾的最大設置量" canImportAntennas: "允許匯入天線" canImportBlocking: "允許匯入封鎖名單" - canImportFollowing: "允許匯入跟隨名單" + canImportFollowing: "允許匯入追隨名單" canImportMuting: "允許匯入靜音名單" canImportUserLists: "允許匯入清單" _condition: @@ -2069,7 +2077,7 @@ _2fa: step4: "從現在開始,任何登入操作都將要求您提供權杖。" securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。" registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。" - securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。" + securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全金鑰,以及裝置上的生物辨識、PIN 碼和密碼等來登入。" registerSecurityKey: "註冊安全金鑰或 Passkey" securityKeyName: "輸入金鑰名稱" tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或 Passkey。" @@ -2287,7 +2295,7 @@ _profile: metadataEdit: "編輯附加資訊" metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。" metadataLabel: "標籤" - metadataContent: "内容" + metadataContent: "內容" changeAvatar: "更換大頭貼" changeBanner: "變更橫幅圖像" verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL,欄位旁邊將出現驗證圖示。" @@ -2627,7 +2635,7 @@ _externalResourceInstaller: description: "已取得資料但解析 AiScript 時發生錯誤,導致無法載入。請聯絡外掛作者。請檢查 Javascript 控制台以取得錯誤詳細資訊。" _pluginInstallFailed: title: "外掛安裝失敗" - description: "安裝插件時出現問題。請再試一次。請參閱 Javascript 控制台以取得錯誤詳細資訊。" + description: "安裝外掛時出現問題。請再試一次。可參閱 Javascript 控制台以取得錯誤詳細資訊。" _themeParseFailed: title: "佈景主題解析錯誤" description: "已取得資料但解析佈景主題時發生錯誤,導致無法載入。請聯絡佈景主題的作者。請檢查 Javascript 控制台以取得錯誤詳細資訊。" @@ -2646,7 +2654,7 @@ _dataSaver: description: "將不再自動載入網址預覽縮圖。" _code: title: "程式碼突出顯示" - description: "如果使用了 MFM 的程式碼突顯標記,則在點擊之前不會載入。程式碼突顯要求加載每種程式語言的突顯定義檔案,但由於這些檔案不再自動載入,因此有望減少資料流量。" + description: "如果使用了程式碼突顯語法(如 MFM),則在點擊之前不會被載入。由於需要為對應的程式語言下載突顯定義檔案,因此關閉自動載入有助於減少資料流量。" _hemisphere: N: "北半球" S: "南半球" @@ -2721,6 +2729,66 @@ _contextMenu: app: "應用程式" appWithShift: "Shift 鍵應用程式" native: "瀏覽器的使用者介面" +_gridComponent: + _error: + requiredValue: "此值為必填欄位" + columnTypeNotSupport: "正規表達式驗證僅支援 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: "將選取範圍的行設為刪除對象\n" + alertUpdateEmojisNothingDescription: "沒有選取需要變更的表情符號。" + alertDeleteEmojisNothingDescription: "沒有選取需要刪除的表情符號。" + confirmMovePage: "要移動到其他頁面嗎?" + confirmChangeView: "要更改顯示方式嗎?" + confirmUpdateEmojisDescription: "將更新{count}個表情符號。是否執行此操作?" + confirmDeleteEmojisDescription: "將刪除勾選的{count}個表情符號。是否執行此操作?" + confirmResetDescription: "目前所做的所有變更都會重設。" + confirmMovePageDesciption: "此頁面的表情符號已被更改。 \n若未儲存就直接離開此頁面,則在此頁面進行的所有更改將會被捨棄。" + dialogSelectRoleTitle: "根據表情符號設定的角色進行搜尋" + _register: + uploadSettingTitle: "上傳設定" + uploadSettingDescription: "您可以在此畫面設定表情符號上傳時的操作。" + directoryToCategoryLabel: "在「類別」欄位中輸入目錄名稱" + directoryToCategoryCaption: "拖放目錄時,請在「類別」欄位中輸入目錄名稱。" + emojiInputAreaCaption: "以下列其中一種方式選擇您想要註冊的表情符號" + emojiInputAreaList1: "將圖片檔案或目錄拖放到此框中" + emojiInputAreaList2: "點擊此連結從電腦中選擇" + emojiInputAreaList3: "點擊此連結從雲端硬碟中選擇" + confirmRegisterEmojisDescription: "將列表中顯示的表情符號登錄為新的自定表情符號。是否確定?(為避免過高負荷,每次操作最多可登錄{count}個表情符號)" + confirmClearEmojisDescription: "放棄編輯內容並清除列表中顯示的表情符號。是否確定?" + confirmUploadEmojisDescription: "將拖放的{count}個檔案上傳到雲端硬碟。是否執行此操作?" _embedCodeGen: title: "自訂嵌入程式碼" header: "檢視標頭 " @@ -2744,3 +2812,34 @@ _selfXssPrevention: _followRequest: recieved: "收到的請求" sent: "送出的請求" +_remoteLookupErrors: + _federationNotAllowed: + title: "無法與這個伺服器通訊" + description: "與此伺服器的通訊可能被停用、或封鎖了該伺服器,或被該伺服器封鎖。\n請聯繫您的伺服器管理員。" + _uriInvalid: + title: "URI 不正確" + description: "輸入的 URI 有問題。請檢查是否輸入了 URI 中不能使用的字元。" + _requestFailed: + title: "請求失敗" + description: "與此伺服器的通訊失敗。可能是對方伺服器斷線。 此外,請檢查是否輸入了不正確或不存在的 URI。" + _responseInvalid: + title: "回應不正確" + description: "雖然能夠與這個伺服器通訊,但是取得的資料不正確。" + _responseInvalidIdHostNotMatch: + description: "輸入的 URI 的網域與最終取得的 URI 的網域不同。 如果您是透過第三方伺服器查詢遠端內容,請使用可在原始伺服器上取得的 URI 再次查詢。" + _noSuchObject: + title: "查無項目" + description: "無法找到所要求的資源,請再次檢查 URI。" +_captcha: + verify: "請通過 CAPTCHA 驗證" + testSiteKeyMessage: "可以輸入網站金鑰和秘密金鑰的測試值來檢查預覽。\n詳細資訊請參閱以下頁面。" + _error: + _requestFailed: + title: "CAPTCHA 請求失敗" + text: "請過一段時間後再執行,或再次檢查設定。" + _verificationFailed: + title: "CAPTCHA 驗證失敗" + text: "請再次檢查設定是否正確。" + _unknown: + title: "CAPTCHA 錯誤" + text: "發生了意外的錯誤。" diff --git a/package.json b/package.json index 60de6f4e15ec..bddb4f85a282 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.11.0", + "version": "2025.1.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1709126576000-optimize-emoji-index.js b/packages/backend/migration/1709126576000-optimize-emoji-index.js new file mode 100644 index 000000000000..e4184895d033 --- /dev/null +++ b/packages/backend/migration/1709126576000-optimize-emoji-index.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class OptimizeEmojiIndex1709126576000 { + name = 'OptimizeEmojiIndex1709126576000' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_EMOJI_ROLE_IDS" ON "emoji" using gin ("roleIdsThatCanBeUsedThisEmojiAsReaction")`) + await queryRunner.query(`CREATE INDEX "IDX_EMOJI_CATEGORY" ON "emoji" ("category")`) + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "IDX_EMOJI_CATEGORY"`) + await queryRunner.query(`DROP INDEX "IDX_EMOJI_ROLE_IDS"`) + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index f56a737eea0d..757912755ac9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -134,8 +134,8 @@ "json5": "2.2.3", "jsonld": "8.3.2", "jsrsasign": "11.1.0", - "meilisearch": "0.45.0", "juice": "11.0.0", + "meilisearch": "0.45.0", "mfm-js": "0.24.0", "microformats-parser": "2.0.2", "mime-types": "2.1.35", @@ -146,7 +146,7 @@ "nested-property": "4.0.0", "node-fetch": "3.3.2", "nodemailer": "6.9.16", - "nsfwjs": "2.4.2", + "nsfwjs": "4.2.0", "oauth": "0.10.0", "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", @@ -158,7 +158,6 @@ "probe-image-size": "7.2.3", "promise-limit": "2.7.0", "pug": "3.0.3", - "punycode": "2.3.1", "qrcode": "1.5.4", "random-seed": "0.3.0", "ratelimiter": "3.4.1", @@ -215,7 +214,6 @@ "@types/oauth2orize-pkce": "0.1.2", "@types/pg": "8.11.10", "@types/pug": "2.0.10", - "@types/punycode": "2.1.4", "@types/qrcode": "1.5.5", "@types/random-seed": "0.3.5", "@types/ratelimiter": "3.4.6", diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js index bb149444b555..96c4549ccbdc 100644 --- a/packages/backend/scripts/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -53,4 +53,4 @@ const promises = Array connectToPostgres() ]); -await Promise.allSettled(promises); +await Promise.all(promises); diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 6ae8ccfbb32c..ace7f7841c03 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -7,14 +7,14 @@ import { Global, Inject, Module } from '@nestjs/common'; import * as Redis from 'ioredis'; import { DataSource } from 'typeorm'; import { MeiliSearch } from 'meilisearch'; +import { MiMeta } from '@/models/Meta.js'; import { DI } from './di-symbols.js'; import { Config, loadConfig } from './config.js'; import { createPostgresDataSource } from './postgres.js'; import { RepositoryModule } from './models/RepositoryModule.js'; import { allSettled } from './misc/promise-tracker.js'; -import type { Provider, OnApplicationShutdown } from '@nestjs/common'; -import { MiMeta } from '@/models/Meta.js'; import { GlobalEvents } from './core/GlobalEventService.js'; +import type { Provider, OnApplicationShutdown } from '@nestjs/common'; const $config: Provider = { provide: DI.config, @@ -33,7 +33,11 @@ const $db: Provider = { const $meilisearch: Provider = { provide: DI.meilisearch, useFactory: (config: Config) => { - if (config.meilisearch) { + if (config.fulltextSearch?.provider === 'meilisearch') { + if (!config.meilisearch) { + throw new Error('MeiliSearch is enabled but no configuration is provided'); + } + return new MeiliSearch({ host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`, apiKey: config.meilisearch.apiKey, diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 25375c3015f7..da585ad68d0e 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -68,16 +68,22 @@ process.on('exit', code => { //#endregion -if (cluster.isPrimary || envOption.disableClustering) { - await masterMain(); - +if (!envOption.disableClustering) { if (cluster.isPrimary) { + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); ev.mount(); + } else if (cluster.isWorker) { + logger.info(`Start worker process... pid: ${process.pid}`); + await workerMain(); + } else { + throw new Error('Unknown process type'); } -} - -if (cluster.isWorker || envOption.disableClustering) { - await workerMain(); +} else { + // 非clusterの場合はMasterのみが起動するため、Workerの処理は行わない(cluster.isWorker === trueの状態でこのブロックに来ることはない) + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); + ev.mount(); } readyRef.value = true; diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 4bc5c799cfb9..d1fb3858db5c 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -91,25 +91,36 @@ export async function masterMain() { }); } - if (envOption.disableClustering) { + bootLogger.info( + `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]`, + ); + + if (!envOption.disableClustering) { + // clusterモジュール有効時 + if (envOption.onlyServer) { - await server(); + // onlyServer かつ enableCluster な場合、メインプロセスはforkのみに制限する(listenしない)。 + // ワーカープロセス側でlistenすると、メインプロセスでポートへの着信を受け入れてワーカープロセスへの分配を行う動作をする。 + // そのため、メインプロセスでも直接listenするとポートの競合が発生して起動に失敗してしまう。 + // see: https://nodejs.org/api/cluster.html#cluster } else if (envOption.onlyQueue) { await jobQueue(); } else { await server(); - await jobQueue(); } + + await spawnWorkers(config.clusterLimit); } else { + // clusterモジュール無効時 + if (envOption.onlyServer) { - // nop + await server(); } else if (envOption.onlyQueue) { - // nop + await jobQueue(); } else { await server(); + await jobQueue(); } - - await spawnWorkers(config.clusterLimit); } if (envOption.onlyQueue) { diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 42f1033b9d0a..c0b148480411 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -50,6 +50,9 @@ type Source = { redisForJobQueue?: RedisOptionsSource; redisForTimelines?: RedisOptionsSource; redisForReactions?: RedisOptionsSource; + fulltextSearch?: { + provider?: FulltextSearchProvider; + }; meilisearch?: { host: string; port: string; @@ -99,6 +102,13 @@ type Source = { perUserNotificationsMaxCount?: number; deactivateAntennaThreshold?: number; pidFile: string; + + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } }; export type Config = { @@ -124,6 +134,9 @@ export type Config = { user: string; pass: string; }[] | undefined; + fulltextSearch?: { + provider?: FulltextSearchProvider; + }; meilisearch: { host: string; port: string; @@ -151,6 +164,12 @@ export type Config = { inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; signToActivityPubGet: boolean | undefined; + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } version: string; publishTarballInsteadOfProvideRepositoryUrl: boolean; @@ -184,6 +203,8 @@ export type Config = { pidFile: string; }; +export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch'; + const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -252,6 +273,7 @@ export function loadConfig(): Config { db: { ...config.db, db: dbDb, user: dbUser, pass: dbPass }, dbReplications: config.dbReplications, dbSlaves: config.dbSlaves, + fulltextSearch: config.fulltextSearch, meilisearch: config.meilisearch, redis, redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, @@ -293,6 +315,7 @@ export function loadConfig(): Config { perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), pidFile: config.pidFile, + logging: config.logging, }; } diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index e3a61861f425..1ca039720682 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -26,6 +26,18 @@ export const DB_MAX_NOTE_TEXT_LENGTH = 8192; export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; //#endregion +export const FILE_TYPE_IMAGE = [ + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/avif', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', +]; + // ブラウザで直接表示することを許可するファイルの種類のリスト // ここに含まれないものは application/octet-stream としてレスポンスされる // SVGはXSSを生むので許可しない diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index 742e2621fd81..9bca7954797d 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -160,22 +160,22 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { }; }); - const recipientWebhookIds = await this.fetchWebhookRecipients() - .then(it => it - .filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook') - .map(it => it.systemWebhookId) - .filter(x => x != null)); - for (const webhookId of recipientWebhookIds) { - await Promise.all( - convertedReports.map(it => { - return this.systemWebhookService.enqueueSystemWebhook( - webhookId, - type, - it, - ); - }), - ); - } + const inactiveRecipients = await this.fetchWebhookRecipients() + .then(it => it.filter(it => !it.isActive)); + const withoutWebhookIds = inactiveRecipients + .map(it => it.systemWebhookId) + .filter(x => x != null); + return Promise.all( + convertedReports.map(it => { + return this.systemWebhookService.enqueueSystemWebhook( + type, + it, + { + excludes: withoutWebhookIds, + }, + ); + }), + ); } /** diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index ad852fdd6e6e..248a9b8979a6 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -10,12 +10,13 @@ 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'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; +const REQUIRED_CPU_FLAGS_X64 = ['avx2', 'fma']; let isSupportedCpu: undefined | boolean = undefined; @Injectable() @@ -28,11 +29,10 @@ export class AiService { } @bindThis - public async detectSensitive(path: string): Promise { + public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { - const cpuFlags = await this.getCpuFlags(); - isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); + isSupportedCpu = await this.computeIsSupportedCpu(); } if (!isSupportedCpu) { @@ -41,6 +41,7 @@ export class AiService { } const tf = await import('@tensorflow/tfjs-node'); + tf.env().global.fetch = fetch; if (this.model == null) { await this.modelLoadMutex.runExclusive(async () => { @@ -64,6 +65,22 @@ export class AiService { } } + private async computeIsSupportedCpu(): Promise { + switch (process.arch) { + case 'x64': { + const cpuFlags = await this.getCpuFlags(); + return REQUIRED_CPU_FLAGS_X64.every(required => cpuFlags.includes(required)); + } + case 'arm64': { + // As far as I know, no required CPU flags for ARM64. + return true; + } + default: { + return false; + } + } + } + @bindThis private async getCpuFlags(): Promise { const str = await si.cpuFlags(); diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 206d0dbe0aaf..8c7f66236e27 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -6,6 +6,65 @@ import { Injectable } from '@nestjs/common'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { MiMeta } from '@/models/Meta.js'; +import Logger from '@/logger.js'; +import { LoggerService } from './LoggerService.js'; + +export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const; +export type CaptchaProvider = typeof supportedCaptchaProviders[number]; + +export const captchaErrorCodes = { + invalidProvider: Symbol('invalidProvider'), + invalidParameters: Symbol('invalidParameters'), + noResponseProvided: Symbol('noResponseProvided'), + requestFailed: Symbol('requestFailed'), + verificationFailed: Symbol('verificationFailed'), + unknown: Symbol('unknown'), +} as const; +export type CaptchaErrorCode = typeof captchaErrorCodes[keyof typeof captchaErrorCodes]; + +export type CaptchaSetting = { + provider: CaptchaProvider; + hcaptcha: { + siteKey: string | null; + secretKey: string | null; + } + mcaptcha: { + siteKey: string | null; + secretKey: string | null; + instanceUrl: string | null; + } + recaptcha: { + siteKey: string | null; + secretKey: string | null; + } + turnstile: { + siteKey: string | null; + secretKey: string | null; + } +} + +export class CaptchaError extends Error { + public readonly code: CaptchaErrorCode; + public readonly cause?: unknown; + + constructor(code: CaptchaErrorCode, message: string, cause?: unknown) { + super(message); + this.code = code; + this.cause = cause; + this.name = 'CaptchaError'; + } +} + +export type CaptchaSaveSuccess = { + success: true; +} +export type CaptchaSaveFailure = { + success: false; + error: CaptchaError; +} +export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure; type CaptchaResponse = { success: boolean; @@ -14,9 +73,14 @@ type CaptchaResponse = { @Injectable() export class CaptchaService { + private readonly logger: Logger; + constructor( private httpRequestService: HttpRequestService, + private metaService: MetaService, + loggerService: LoggerService, ) { + this.logger = loggerService.getLogger('captcha'); } @bindThis @@ -44,32 +108,32 @@ export class CaptchaService { @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('recaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'recaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => { - throw new Error(`recaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `recaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`recaptcha-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `recaptcha-failed: ${errorCodes}`); } } @bindThis public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('hcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'hcaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => { - throw new Error(`hcaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `hcaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`hcaptcha-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `hcaptcha-failed: ${errorCodes}`); } } @@ -77,7 +141,7 @@ export class CaptchaService { @bindThis public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('mcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'mcaptcha-failed: no response provided'); } const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost); @@ -91,46 +155,251 @@ export class CaptchaService { headers: { 'Content-Type': 'application/json', }, - }); + }, { throwErrorWhenResponseNotOk: false }); if (result.status !== 200) { - throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK'); + throw new CaptchaError(captchaErrorCodes.requestFailed, 'mcaptcha-failed: mcaptcha didn\'t return 200 OK'); } const resp = (await result.json()) as { valid: boolean }; if (!resp.valid) { - throw new Error('mcaptcha-request-failed'); + throw new CaptchaError(captchaErrorCodes.verificationFailed, 'mcaptcha-request-failed'); } } @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('turnstile-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'turnstile-failed: no response provided'); } const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { - throw new Error(`turnstile-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `turnstile-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`turnstile-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `turnstile-failed: ${errorCodes}`); } } @bindThis public async verifyTestcaptcha(response: string | null | undefined): Promise { if (response == null) { - throw new Error('testcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'testcaptcha-failed: no response provided'); } const success = response === 'testcaptcha-passed'; if (!success) { - throw new Error('testcaptcha-failed'); + throw new CaptchaError(captchaErrorCodes.verificationFailed, 'testcaptcha-failed'); + } + } + + @bindThis + public async get(): Promise { + const meta = await this.metaService.fetch(true); + + let provider: CaptchaProvider; + switch (true) { + case meta.enableHcaptcha: { + provider = 'hcaptcha'; + break; + } + case meta.enableMcaptcha: { + provider = 'mcaptcha'; + break; + } + case meta.enableRecaptcha: { + provider = 'recaptcha'; + break; + } + case meta.enableTurnstile: { + provider = 'turnstile'; + break; + } + case meta.enableTestcaptcha: { + provider = 'testcaptcha'; + break; + } + default: { + provider = 'none'; + break; + } + } + + return { + provider: provider, + hcaptcha: { + siteKey: meta.hcaptchaSiteKey, + secretKey: meta.hcaptchaSecretKey, + }, + mcaptcha: { + siteKey: meta.mcaptchaSitekey, + secretKey: meta.mcaptchaSecretKey, + instanceUrl: meta.mcaptchaInstanceUrl, + }, + recaptcha: { + siteKey: meta.recaptchaSiteKey, + secretKey: meta.recaptchaSecretKey, + }, + turnstile: { + siteKey: meta.turnstileSiteKey, + secretKey: meta.turnstileSecretKey, + }, + }; + } + + /** + * captchaの設定を更新します. その際、フロントエンド側で受け取ったcaptchaからの戻り値を検証し、passした場合のみ設定を更新します. + * 実際の検証処理はサービス内で定義されている各captchaプロバイダの検証関数に委譲します. + * + * @param provider 検証するcaptchaのプロバイダ + * @param params + * @param params.sitekey hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsitekey. それ以外のプロバイダでは無視されます + * @param params.secret hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsecret. それ以外のプロバイダでは無視されます + * @param params.instanceUrl mcaptchaの場合に指定するインスタンスのURL. それ以外のプロバイダでは無視されます + * @param params.captchaResult フロントエンド側で受け取ったcaptchaプロバイダからの戻り値. この値を使ってサーバサイドでの検証を行います + * @see verifyHcaptcha + * @see verifyMcaptcha + * @see verifyRecaptcha + * @see verifyTurnstile + * @see verifyTestcaptcha + */ + @bindThis + public async save( + provider: CaptchaProvider, + params?: { + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + captchaResult?: string | null; + }, + ): Promise { + if (!supportedCaptchaProviders.includes(provider)) { + return { + success: false, + error: new CaptchaError(captchaErrorCodes.invalidProvider, `Invalid captcha provider: ${provider}`), + }; + } + + const operation = { + none: async () => { + await this.updateMeta(provider, params); + }, + hcaptcha: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'hcaptcha-failed: secret and captureResult are required'); + } + + await this.verifyHcaptcha(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + mcaptcha: async () => { + if (!params?.secret || !params.sitekey || !params.instanceUrl || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'mcaptcha-failed: secret, sitekey, instanceUrl and captureResult are required'); + } + + await this.verifyMcaptcha(params.secret, params.sitekey, params.instanceUrl, params.captchaResult); + await this.updateMeta(provider, params); + }, + recaptcha: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'recaptcha-failed: secret and captureResult are required'); + } + + await this.verifyRecaptcha(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + turnstile: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: secret and captureResult are required'); + } + + await this.verifyTurnstile(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + testcaptcha: async () => { + if (!params?.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: captureResult are required'); + } + + await this.verifyTestcaptcha(params.captchaResult); + await this.updateMeta(provider, params); + }, + }[provider]; + + return operation() + .then(() => ({ success: true }) as CaptchaSaveSuccess) + .catch(err => { + this.logger.info(err); + const error = err instanceof CaptchaError + ? err + : new CaptchaError(captchaErrorCodes.unknown, `unknown error: ${err}`); + return { + success: false, + error, + }; + }); + } + + @bindThis + private async updateMeta( + provider: CaptchaProvider, + params?: { + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + }, + ) { + const metaPartial: Partial< + Pick< + MiMeta, + ('enableHcaptcha' | 'hcaptchaSiteKey' | 'hcaptchaSecretKey') | + ('enableMcaptcha' | 'mcaptchaSitekey' | 'mcaptchaSecretKey' | 'mcaptchaInstanceUrl') | + ('enableRecaptcha' | 'recaptchaSiteKey' | 'recaptchaSecretKey') | + ('enableTurnstile' | 'turnstileSiteKey' | 'turnstileSecretKey') | + ('enableTestcaptcha') + > + > = { + enableHcaptcha: provider === 'hcaptcha', + enableMcaptcha: provider === 'mcaptcha', + enableRecaptcha: provider === 'recaptcha', + enableTurnstile: provider === 'turnstile', + enableTestcaptcha: provider === 'testcaptcha', + }; + + const updateIfNotUndefined = (key: K, value: typeof metaPartial[K]) => { + if (value !== undefined) { + metaPartial[key] = value; + } + }; + switch (provider) { + case 'hcaptcha': { + updateIfNotUndefined('hcaptchaSiteKey', params?.sitekey); + updateIfNotUndefined('hcaptchaSecretKey', params?.secret); + break; + } + case 'mcaptcha': { + updateIfNotUndefined('mcaptchaSitekey', params?.sitekey); + updateIfNotUndefined('mcaptchaSecretKey', params?.secret); + updateIfNotUndefined('mcaptchaInstanceUrl', params?.instanceUrl); + break; + } + case 'recaptcha': { + updateIfNotUndefined('recaptchaSiteKey', params?.sitekey); + updateIfNotUndefined('recaptchaSecretKey', params?.secret); + break; + } + case 'turnstile': { + updateIfNotUndefined('turnstileSiteKey', params?.sitekey); + updateIfNotUndefined('turnstileSecretKey', params?.secret); + break; + } } + + await this.metaService.update(metaPartial); } } diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 45661134491e..da71a5de6f8a 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -4,24 +4,59 @@ */ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; -import { In, IsNull } from 'typeorm'; import * as Redis from 'ioredis'; -import { DI } from '@/di-symbols.js'; -import { IdService } from '@/core/IdService.js'; +import { In, IsNull } from 'typeorm'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiEmoji } from '@/models/Emoji.js'; -import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { query } from '@/misc/prelude/url.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js'; +import type { MiEmoji } from '@/models/Emoji.js'; import type { Serialized } from '@/types.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/; +export const fetchEmojisHostTypes = [ + 'local', + 'remote', + 'all', +] as const; +export type FetchEmojisHostTypes = typeof fetchEmojisHostTypes[number]; +export const fetchEmojisSortKeys = [ + '+id', + '-id', + '+updatedAt', + '-updatedAt', + '+name', + '-name', + '+host', + '-host', + '+uri', + '-uri', + '+publicUrl', + '-publicUrl', + '+type', + '-type', + '+aliases', + '-aliases', + '+category', + '-category', + '+license', + '-license', + '+isSensitive', + '-isSensitive', + '+localOnly', + '-localOnly', + '+roleIdsThatCanBeUsedThisEmojiAsReaction', + '-roleIdsThatCanBeUsedThisEmojiAsReaction', +] as const; +export type FetchEmojisSortKeys = typeof fetchEmojisSortKeys[number]; + @Injectable() export class CustomEmojiService implements OnApplicationShutdown { private emojisCache: MemoryKVCache; @@ -30,10 +65,8 @@ export class CustomEmojiService implements OnApplicationShutdown { constructor( @Inject(DI.redis) private redisClient: Redis.Redis, - @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, - private utilityService: UtilityService, private idService: IdService, private emojiEntityService: EmojiEntityService, @@ -58,7 +91,9 @@ export class CustomEmojiService implements OnApplicationShutdown { @bindThis public async add(data: { - driveFile: MiDriveFile; + originalUrl: string; + publicUrl: string; + fileType: string; name: string; category: string | null; aliases: string[]; @@ -75,9 +110,9 @@ export class CustomEmojiService implements OnApplicationShutdown { category: data.category, host: data.host, aliases: data.aliases, - originalUrl: data.driveFile.url, - publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url, - type: data.driveFile.webpublicType ?? data.driveFile.type, + originalUrl: data.originalUrl, + publicUrl: data.publicUrl, + type: data.fileType, license: data.license, isSensitive: data.isSensitive, localOnly: data.localOnly, @@ -105,8 +140,10 @@ export class CustomEmojiService implements OnApplicationShutdown { @bindThis public async update(data: ( { id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], } - ) & { - driveFile?: MiDriveFile; + ) & { + originalUrl?: string; + publicUrl?: string; + fileType?: string; category?: string | null; aliases?: string[]; license?: string | null; @@ -139,9 +176,9 @@ export class CustomEmojiService implements OnApplicationShutdown { license: data.license, isSensitive: data.isSensitive, localOnly: data.localOnly, - originalUrl: data.driveFile != null ? data.driveFile.url : undefined, - publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined, - type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined, + originalUrl: data.originalUrl, + publicUrl: data.publicUrl, + type: data.fileType, roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined, }); @@ -308,7 +345,7 @@ export class CustomEmojiService implements OnApplicationShutdown { @bindThis private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { - // クエリに使うホスト + // クエリに使うホスト let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) : src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない) : this.utilityService.isSelfHost(src) ? null // 自ホスト指定 @@ -414,6 +451,151 @@ export class CustomEmojiService implements OnApplicationShutdown { return this.emojisRepository.findOneBy({ name, host: IsNull() }); } + @bindThis + public async fetchEmojis( + params?: { + query?: { + updatedAtFrom?: string; + updatedAtTo?: string; + name?: string; + host?: string; + uri?: string; + publicUrl?: string; + type?: string; + aliases?: string; + category?: string; + license?: string; + isSensitive?: boolean; + localOnly?: boolean; + hostType?: FetchEmojisHostTypes; + roleIds?: string[]; + }, + sinceId?: string; + untilId?: string; + }, + opts?: { + limit?: number; + page?: number; + sortKeys?: FetchEmojisSortKeys[] + }, + ) { + function multipleWordsToQuery(words: string) { + return words.split(/\s/).filter(x => x.length > 0).map(x => `%${sqlLikeEscape(x)}%`); + } + + const builder = this.emojisRepository.createQueryBuilder('emoji'); + if (params?.query) { + const q = params.query; + if (q.updatedAtFrom) { + // noIndexScan + builder.andWhere('CAST(emoji.updatedAt AS DATE) >= :updateAtFrom', { updateAtFrom: q.updatedAtFrom }); + } + if (q.updatedAtTo) { + // noIndexScan + builder.andWhere('CAST(emoji.updatedAt AS DATE) <= :updateAtTo', { updateAtTo: q.updatedAtTo }); + } + if (q.name) { + builder.andWhere('emoji.name ~~ ANY(ARRAY[:...name])', { name: multipleWordsToQuery(q.name) }); + } + + switch (true) { + case q.hostType === 'local': { + builder.andWhere('emoji.host IS NULL'); + break; + } + case q.hostType === 'remote': { + if (q.host) { + // noIndexScan + builder.andWhere('emoji.host ~~ ANY(ARRAY[:...host])', { host: multipleWordsToQuery(q.host) }); + } else { + builder.andWhere('emoji.host IS NOT NULL'); + } + break; + } + } + + if (q.uri) { + // noIndexScan + builder.andWhere('emoji.uri ~~ ANY(ARRAY[:...uri])', { uri: multipleWordsToQuery(q.uri) }); + } + if (q.publicUrl) { + // noIndexScan + builder.andWhere('emoji.publicUrl ~~ ANY(ARRAY[:...publicUrl])', { publicUrl: multipleWordsToQuery(q.publicUrl) }); + } + if (q.type) { + // noIndexScan + builder.andWhere('emoji.type ~~ ANY(ARRAY[:...type])', { type: multipleWordsToQuery(q.type) }); + } + if (q.aliases) { + // noIndexScan + const subQueryBuilder = builder.subQuery() + .select('COUNT(0)', 'count') + .from( + sq2 => sq2 + .select('unnest(subEmoji.aliases)', 'alias') + .addSelect('subEmoji.id', 'id') + .from('emoji', 'subEmoji'), + 'aliasTable', + ) + .where('"emoji"."id" = "aliasTable"."id"') + .andWhere('"aliasTable"."alias" ~~ ANY(ARRAY[:...aliases])', { aliases: multipleWordsToQuery(q.aliases) }); + + builder.andWhere(`(${subQueryBuilder.getQuery()}) > 0`); + } + if (q.category) { + builder.andWhere('emoji.category ~~ ANY(ARRAY[:...category])', { category: multipleWordsToQuery(q.category) }); + } + if (q.license) { + // noIndexScan + builder.andWhere('emoji.license ~~ ANY(ARRAY[:...license])', { license: multipleWordsToQuery(q.license) }); + } + if (q.isSensitive != null) { + // noIndexScan + builder.andWhere('emoji.isSensitive = :isSensitive', { isSensitive: q.isSensitive }); + } + if (q.localOnly != null) { + // noIndexScan + builder.andWhere('emoji.localOnly = :localOnly', { localOnly: q.localOnly }); + } + if (q.roleIds && q.roleIds.length > 0) { + builder.andWhere('emoji.roleIdsThatCanBeUsedThisEmojiAsReaction && ARRAY[:...roleIds]::VARCHAR[]', { roleIds: q.roleIds }); + } + } + + if (params?.sinceId) { + builder.andWhere('emoji.id > :sinceId', { sinceId: params.sinceId }); + } + if (params?.untilId) { + builder.andWhere('emoji.id < :untilId', { untilId: params.untilId }); + } + + if (opts?.sortKeys && opts.sortKeys.length > 0) { + for (const sortKey of opts.sortKeys) { + const direction = sortKey.startsWith('-') ? 'DESC' : 'ASC'; + const key = sortKey.replace(/^[+-]/, ''); + builder.addOrderBy(`emoji.${key}`, direction); + } + } else { + builder.addOrderBy('emoji.id', 'DESC'); + } + + const limit = opts?.limit ?? 10; + if (opts?.page) { + builder.skip((opts.page - 1) * limit); + } + + builder.take(limit); + + const [emojis, count] = await builder.getManyAndCount(); + + return { + emojis, + count: (count > limit ? emojis.length : count), + allCount: count, + allPages: Math.ceil(count / limit), + }; + } + @bindThis public dispose(): void { this.emojisCache.dispose(); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 987999bce7d4..ce3af7c77447 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -181,7 +181,7 @@ 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; @@ -206,7 +206,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise { + private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise { const url = 'https://' + instance.host; if (doc) { @@ -232,7 +232,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async fetchIconUrl(instance: MiInstance, doc: Document | 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; @@ -261,7 +261,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getThemeColor(info: NodeInfo | null, doc: Document | 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 +273,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeName === 'string') { return info.metadata.nodeName; @@ -298,7 +298,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getDescription(info: NodeInfo | null, doc: Document | 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 6bd6cb8d9bce..fc68eb48360a 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -13,7 +13,6 @@ import * as fileType from 'file-type'; import FFmpeg from 'fluent-ffmpeg'; import isSvg from 'is-svg'; import probeImageSize from 'probe-image-size'; -import { type predictionType } from 'nsfwjs'; import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import * as blurhash from 'blurhash'; import { createTempDir } from '@/misc/create-temp.js'; @@ -21,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 type { PredictionType } from 'nsfwjs'; export type FileInfo = { size: number; @@ -170,7 +170,7 @@ export class FileInfoService { let sensitive = false; let porn = false; - function judgePrediction(result: readonly predictionType[]): [sensitive: boolean, porn: boolean] { + function judgePrediction(result: readonly PredictionType[]): [sensitive: boolean, porn: boolean] { let sensitive = false; let porn = false; diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 8061622340b6..bf06d4457e2a 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -171,6 +171,39 @@ export class MfmService { break; } + case 'ruby': { + let ruby: [string, string][] = []; + for (const child of node.childNodes) { + if (child.nodeName === 'rp') { + continue; + } + if (treeAdapter.isTextNode(child) && !/\s|\[|\]/.test(child.value)) { + ruby.push([child.value, '']); + continue; + } + if (child.nodeName === '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); + break; + } else { + ruby.at(-1)![1] = rt; + continue; + } + } + // If any other element is included in ruby, it is treated as a normal text + ruby = []; + appendChildren(node.childNodes); + break; + } + for (const [base, rt] of ruby) { + text += `$[ruby ${base} ${rt}]`; + } + break; + } + // block code (
)
 				case 'pre': {
 					if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 56ddcefd7c46..8a79908e8263 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -614,14 +614,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 			this.roleService.addNoteToRoleTimeline(noteObj);
 
-			this.webhookService.getActiveWebhooks().then(webhooks => {
-				webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
-				for (const webhook of webhooks) {
-					this.queueService.userWebhookDeliver(webhook, 'note', {
-						note: noteObj,
-					});
-				}
-			});
+			this.webhookService.enqueueUserWebhook(user.id, 'note', { note: noteObj });
 
 			const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
 
@@ -641,13 +634,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 					if (!isThreadMuted) {
 						nm.push(data.reply.userId, 'reply');
 						this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
-
-						const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
-						for (const webhook of webhooks) {
-							this.queueService.userWebhookDeliver(webhook, 'reply', {
-								note: noteObj,
-							});
-						}
+						this.webhookService.enqueueUserWebhook(data.reply.userId, 'reply', { note: noteObj });
 					}
 				}
 			}
@@ -664,20 +651,14 @@ export class NoteCreateService implements OnApplicationShutdown {
 				// Publish event
 				if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
 					this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj);
-
-					const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
-					for (const webhook of webhooks) {
-						this.queueService.userWebhookDeliver(webhook, 'renote', {
-							note: noteObj,
-						});
-					}
+					this.webhookService.enqueueUserWebhook(data.renote.userId, 'renote', { note: noteObj });
 				}
 			}
 
 			nm.notify();
 
 			//#region AP deliver
-			if (this.userEntityService.isLocalUser(user)) {
+			if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
 				(async () => {
 					const noteActivity = await this.renderNoteOrRenoteActivity(data, note);
 					const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
@@ -796,13 +777,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 
 			this.globalEventService.publishMainStream(u.id, 'mention', detailPackedNote);
-
-			const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
-			for (const webhook of webhooks) {
-				this.queueService.userWebhookDeliver(webhook, 'mention', {
-					note: detailPackedNote,
-				});
-			}
+			this.webhookService.enqueueUserWebhook(u.id, 'mention', { note: detailPackedNote });
 
 			// Create notification
 			nm.push(u.id, 'mention');
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index bb2a463354a7..37721d2bf157 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -28,7 +28,7 @@ export class S3Service {
 			? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}`
 			: `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
 
-		const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy);
+		const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy, true);
 		const handlerOption: NodeHttpHandlerOptions = {};
 		if (meta.objectStorageUseSSL) {
 			handlerOption.httpsAgent = agent as https.Agent;
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index edfc47037523..64e3f2f56a8e 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -6,16 +6,17 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
+import { type Config, FulltextSearchProvider } from '@/config.js';
 import { bindThis } from '@/decorators.js';
 import { MiNote } from '@/models/Note.js';
-import { MiUser } from '@/models/_.js';
 import type { NotesRepository } from '@/models/_.js';
+import { MiUser } from '@/models/_.js';
 import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 import { isUserRelated } from '@/misc/is-user-related.js';
 import { CacheService } from '@/core/CacheService.js';
 import { QueryService } from '@/core/QueryService.js';
 import { IdService } from '@/core/IdService.js';
+import { LoggerService } from '@/core/LoggerService.js';
 import type { Index, MeiliSearch } from 'meilisearch';
 
 type K = string;
@@ -27,12 +28,24 @@ type Q =
 	{ op: '<', k: K, v: number } |
 	{ op: '>=', k: K, v: number } |
 	{ op: '<=', k: K, v: number } |
-	{ op: 'is null', k: K} |
-	{ op: 'is not null', k: K} |
+	{ op: 'is null', k: K } |
+	{ op: 'is not null', k: K } |
 	{ op: 'and', qs: Q[] } |
 	{ op: 'or', qs: Q[] } |
 	{ op: 'not', q: Q };
 
+export type SearchOpts = {
+	userId?: MiNote['userId'] | null;
+	channelId?: MiNote['channelId'] | null;
+	host?: string | null;
+};
+
+export type SearchPagination = {
+	untilId?: MiNote['id'];
+	sinceId?: MiNote['id'];
+	limit: number;
+};
+
 function compileValue(value: V): string {
 	if (typeof value === 'string') {
 		return `'${value}'`; // TODO: escape
@@ -64,7 +77,8 @@ function compileQuery(q: Q): string {
 @Injectable()
 export class SearchService {
 	private readonly meilisearchIndexScope: 'local' | 'global' | string[] = 'local';
-	private meilisearchNoteIndex: Index | null = null;
+	private readonly meilisearchNoteIndex: Index | null = null;
+	private readonly provider: FulltextSearchProvider;
 
 	constructor(
 		@Inject(DI.config)
@@ -79,6 +93,7 @@ export class SearchService {
 		private cacheService: CacheService,
 		private queryService: QueryService,
 		private idService: IdService,
+		private loggerService: LoggerService,
 	) {
 		if (meilisearch) {
 			this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
@@ -109,132 +124,185 @@ export class SearchService {
 		if (config.meilisearch?.scope) {
 			this.meilisearchIndexScope = config.meilisearch.scope;
 		}
+
+		this.provider = config.fulltextSearch?.provider ?? 'sqlLike';
+		this.loggerService.getLogger('SearchService').info(`-- Provider: ${this.provider}`);
 	}
 
 	@bindThis
 	public async indexNote(note: MiNote): Promise {
+		if (!this.meilisearch) return;
 		if (note.text == null && note.cw == null) return;
 		if (!['home', 'public'].includes(note.visibility)) return;
 
-		if (this.meilisearch) {
-			switch (this.meilisearchIndexScope) {
-				case 'global':
-					break;
+		switch (this.meilisearchIndexScope) {
+			case 'global':
+				break;
 
-				case 'local':
-					if (note.userHost == null) break;
-					return;
+			case 'local':
+				if (note.userHost == null) break;
+				return;
 
-				default: {
-					if (note.userHost == null) break;
-					if (this.meilisearchIndexScope.includes(note.userHost)) break;
-					return;
-				}
+			default: {
+				if (note.userHost == null) break;
+				if (this.meilisearchIndexScope.includes(note.userHost)) break;
+				return;
 			}
-
-			await this.meilisearchNoteIndex?.addDocuments([{
-				id: note.id,
-				createdAt: this.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 this.meilisearchNoteIndex?.addDocuments([{
+			id: note.id,
+			createdAt: this.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',
+		});
 	}
 
 	@bindThis
 	public async unindexNote(note: MiNote): Promise {
+		if (!this.meilisearch) return;
 		if (!['home', 'public'].includes(note.visibility)) return;
 
-		if (this.meilisearch) {
-			this.meilisearchNoteIndex!.deleteDocument(note.id);
-		}
+		await this.meilisearchNoteIndex?.deleteDocument(note.id);
 	}
 
 	@bindThis
-	public async searchNote(q: string, me: MiUser | null, opts: {
-		userId?: MiNote['userId'] | null;
-		channelId?: MiNote['channelId'] | null;
-		host?: string | null;
-	}, pagination: {
-		untilId?: MiNote['id'];
-		sinceId?: MiNote['id'];
-		limit?: number;
-	}): Promise {
-		if (this.meilisearch) {
-			const filter: Q = {
-				op: 'and',
-				qs: [],
-			};
-			if (pagination.untilId) filter.qs.push({ op: '<', k: 'createdAt', v: this.idService.parse(pagination.untilId).date.getTime() });
-			if (pagination.sinceId) filter.qs.push({ op: '>', k: 'createdAt', v: this.idService.parse(pagination.sinceId).date.getTime() });
-			if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId });
-			if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
-			if (opts.host) {
-				if (opts.host === '.') {
-					filter.qs.push({ op: 'is null', k: 'userHost' });
-				} else {
-					filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
-				}
+	public async searchNote(
+		q: string,
+		me: MiUser | null,
+		opts: SearchOpts,
+		pagination: SearchPagination,
+	): Promise {
+		switch (this.provider) {
+			case 'sqlLike':
+			case 'sqlPgroonga': {
+				// ほとんど内容に差がないのでsqlLikeとsqlPgroongaを同じ処理にしている.
+				// 今後の拡張で差が出る用であれば関数を分ける.
+				return this.searchNoteByLike(q, me, opts, pagination);
 			}
-			const res = await this.meilisearchNoteIndex!.search(q, {
-				sort: ['createdAt:desc'],
-				matchingStrategy: 'all',
-				attributesToRetrieve: ['id', 'createdAt'],
-				filter: compileQuery(filter),
-				limit: pagination.limit,
-			});
-			if (res.hits.length === 0) return [];
-			const [
-				userIdsWhoMeMuting,
-				userIdsWhoBlockingMe,
-			] = me ? await Promise.all([
-				this.cacheService.userMutingsCache.fetch(me.id),
-				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 => {
-				if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
-				if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
-				return true;
-			});
-			return notes.sort((a, b) => a.id > b.id ? -1 : 1);
+			case 'meilisearch': {
+				return this.searchNoteByMeiliSearch(q, me, opts, pagination);
+			}
+			default: {
+				// eslint-disable-next-line @typescript-eslint/no-unused-vars
+				const typeCheck: never = this.provider;
+				return [];
+			}
+		}
+	}
+
+	@bindThis
+	private async searchNoteByLike(
+		q: string,
+		me: MiUser | null,
+		opts: SearchOpts,
+		pagination: SearchPagination,
+	): Promise {
+		const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+
+		if (opts.userId) {
+			query.andWhere('note.userId = :userId', { userId: opts.userId });
+		} else if (opts.channelId) {
+			query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+		}
+
+		query
+			.innerJoinAndSelect('note.user', 'user')
+			.leftJoinAndSelect('note.reply', 'reply')
+			.leftJoinAndSelect('note.renote', 'renote')
+			.leftJoinAndSelect('reply.user', 'replyUser')
+			.leftJoinAndSelect('renote.user', 'renoteUser');
+
+		if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
+			query.andWhere('note.text &@ :q', { q });
 		} else {
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+			query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
+		}
 
-			if (opts.userId) {
-				query.andWhere('note.userId = :userId', { userId: opts.userId });
-			} else if (opts.channelId) {
-				query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+		if (opts.host) {
+			if (opts.host === '.') {
+				query.andWhere('user.host IS NULL');
+			} else {
+				query.andWhere('user.host = :host', { host: opts.host });
 			}
+		}
 
-			query
-				.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` })
-				.innerJoinAndSelect('note.user', 'user')
-				.leftJoinAndSelect('note.reply', 'reply')
-				.leftJoinAndSelect('note.renote', 'renote')
-				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser');
-
-			if (opts.host) {
-				if (opts.host === '.') {
-					query.andWhere('user.host IS NULL');
-				} else {
-					query.andWhere('user.host = :host', { host: opts.host });
-				}
-			}
+		this.queryService.generateVisibilityQuery(query, me);
+		if (me) this.queryService.generateMutedUserQuery(query, me);
+		if (me) this.queryService.generateBlockedUserQuery(query, me);
+
+		return query.limit(pagination.limit).getMany();
+	}
 
-			this.queryService.generateVisibilityQuery(query, me);
-			if (me) this.queryService.generateMutedUserQuery(query, me);
-			if (me) this.queryService.generateBlockedUserQuery(query, me);
+	@bindThis
+	private async searchNoteByMeiliSearch(
+		q: string,
+		me: MiUser | null,
+		opts: SearchOpts,
+		pagination: SearchPagination,
+	): Promise {
+		if (!this.meilisearch || !this.meilisearchNoteIndex) {
+			throw new Error('MeiliSearch is not available');
+		}
+
+		const filter: Q = {
+			op: 'and',
+			qs: [],
+		};
+		if (pagination.untilId) filter.qs.push({
+			op: '<',
+			k: 'createdAt',
+			v: this.idService.parse(pagination.untilId).date.getTime(),
+		});
+		if (pagination.sinceId) filter.qs.push({
+			op: '>',
+			k: 'createdAt',
+			v: this.idService.parse(pagination.sinceId).date.getTime(),
+		});
+		if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId });
+		if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
+		if (opts.host) {
+			if (opts.host === '.') {
+				filter.qs.push({ op: 'is null', k: 'userHost' });
+			} else {
+				filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
+			}
+		}
 
-			return await query.limit(pagination.limit).getMany();
+		const res = await this.meilisearchNoteIndex.search(q, {
+			sort: ['createdAt:desc'],
+			matchingStrategy: 'all',
+			attributesToRetrieve: ['id', 'createdAt'],
+			filter: compileQuery(filter),
+			limit: pagination.limit,
+		});
+		if (res.hits.length === 0) {
+			return [];
 		}
+
+		const [
+			userIdsWhoMeMuting,
+			userIdsWhoBlockingMe,
+		] = me
+			? await Promise.all([
+				this.cacheService.userMutingsCache.fetch(me.id),
+				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 => {
+			if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
+			if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
+			return true;
+		});
+
+		return notes.sort((a, b) => a.id > b.id ? -1 : 1);
 	}
 }
diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts
index de0016961234..8239490adce5 100644
--- a/packages/backend/src/core/SystemWebhookService.ts
+++ b/packages/backend/src/core/SystemWebhookService.ts
@@ -50,7 +50,6 @@ export type SystemWebhookPayload =
 
 @Injectable()
 export class SystemWebhookService implements OnApplicationShutdown {
-	private logger: Logger;
 	private activeSystemWebhooksFetched = false;
 	private activeSystemWebhooks: MiSystemWebhook[] = [];
 
@@ -62,11 +61,9 @@ export class SystemWebhookService implements OnApplicationShutdown {
 		private idService: IdService,
 		private queueService: QueueService,
 		private moderationLogService: ModerationLogService,
-		private loggerService: LoggerService,
 		private globalEventService: GlobalEventService,
 	) {
 		this.redisForSub.on('message', this.onMessage);
-		this.logger = this.loggerService.getLogger('webhook');
 	}
 
 	@bindThis
@@ -193,28 +190,24 @@ export class SystemWebhookService implements OnApplicationShutdown {
 	/**
 	 * SystemWebhook をWebhook配送キューに追加する
 	 * @see QueueService.systemWebhookDeliver
-	 * // TODO: contentの型を厳格化する
 	 */
 	@bindThis
 	public async enqueueSystemWebhook(
-		webhook: MiSystemWebhook | MiSystemWebhook['id'],
 		type: T,
 		content: SystemWebhookPayload,
+		opts?: {
+			excludes?: MiSystemWebhook['id'][];
+		},
 	) {
-		const webhookEntity = typeof webhook === 'string'
-			? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
-			: webhook;
-		if (!webhookEntity || !webhookEntity.isActive) {
-			this.logger.info(`SystemWebhook is not active or not found : ${webhook}`);
-			return;
-		}
-
-		if (!webhookEntity.on.includes(type)) {
-			this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
-			return;
-		}
-
-		return this.queueService.systemWebhookDeliver(webhookEntity, type, content);
+		const webhooks = await this.fetchActiveSystemWebhooks()
+			.then(webhooks => {
+				return webhooks.filter(webhook => !opts?.excludes?.includes(webhook.id) && webhook.on.includes(type));
+			});
+		return Promise.all(
+			webhooks.map(webhook => {
+				return this.queueService.systemWebhookDeliver(webhook, type, content);
+			}),
+		);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 2f1310b8efe7..8da1bb209281 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -118,13 +118,7 @@ export class UserBlockingService implements OnModuleInit {
 				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
 				this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
-
-				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
-				for (const webhook of webhooks) {
-					this.queueService.userWebhookDeliver(webhook, 'unfollow', {
-						user: packed,
-					});
-				}
+				this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packed });
 			});
 		}
 
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 8963003057cf..b98ca97ec9ec 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -333,13 +333,7 @@ export class UserFollowingService implements OnModuleInit {
 				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
 				this.globalEventService.publishMainStream(follower.id, 'follow', packed);
-
-				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
-				for (const webhook of webhooks) {
-					this.queueService.userWebhookDeliver(webhook, 'follow', {
-						user: packed,
-					});
-				}
+				this.webhookService.enqueueUserWebhook(follower.id, 'follow', { user: packed });
 			});
 		}
 
@@ -347,13 +341,7 @@ export class UserFollowingService implements OnModuleInit {
 		if (this.userEntityService.isLocalUser(followee)) {
 			this.userEntityService.pack(follower.id, followee).then(async packed => {
 				this.globalEventService.publishMainStream(followee.id, 'followed', packed);
-
-				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
-				for (const webhook of webhooks) {
-					this.queueService.userWebhookDeliver(webhook, 'followed', {
-						user: packed,
-					});
-				}
+				this.webhookService.enqueueUserWebhook(followee.id, 'followed', { user: packed });
 			});
 
 			// 通知を作成
@@ -400,13 +388,7 @@ export class UserFollowingService implements OnModuleInit {
 				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
 				this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
-
-				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
-				for (const webhook of webhooks) {
-					this.queueService.userWebhookDeliver(webhook, 'unfollow', {
-						user: packed,
-					});
-				}
+				this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packed });
 			});
 		}
 
@@ -744,13 +726,7 @@ export class UserFollowingService implements OnModuleInit {
 		});
 
 		this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);
-
-		const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
-		for (const webhook of webhooks) {
-			this.queueService.userWebhookDeliver(webhook, 'unfollow', {
-				user: packedFollowee,
-			});
-		}
+		this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packedFollowee });
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
index 9b1961c631cd..1f471513f3d2 100644
--- a/packages/backend/src/core/UserService.ts
+++ b/packages/backend/src/core/UserService.ts
@@ -63,13 +63,6 @@ export class UserService {
 	@bindThis
 	public async notifySystemWebhook(user: MiUser, type: 'userCreated') {
 		const packedUser = await this.userEntityService.pack(user, null, { schema: 'UserLite' });
-		const recipientWebhookIds = await this.systemWebhookService.fetchSystemWebhooks({ isActive: true, on: [type] });
-		for (const webhookId of recipientWebhookIds) {
-			await this.systemWebhookService.enqueueSystemWebhook(
-				webhookId,
-				type,
-				packedUser,
-			);
-		}
+		return this.systemWebhookService.enqueueSystemWebhook(type, packedUser);
 	}
 }
diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts
index 7117a3d7fa70..b1728671aec1 100644
--- a/packages/backend/src/core/UserWebhookService.ts
+++ b/packages/backend/src/core/UserWebhookService.ts
@@ -5,13 +5,14 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import { type WebhooksRepository } from '@/models/_.js';
+import { MiUser, type WebhooksRepository } from '@/models/_.js';
 import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 import { GlobalEvents } from '@/core/GlobalEventService.js';
-import type { OnApplicationShutdown } from '@nestjs/common';
 import type { Packed } from '@/misc/json-schema.js';
+import { QueueService } from '@/core/QueueService.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
 
 export type UserWebhookPayload =
 	T extends 'note' | 'reply' | 'renote' |'mention' ? {
@@ -34,6 +35,7 @@ export class UserWebhookService implements OnApplicationShutdown {
 		private redisForSub: Redis.Redis,
 		@Inject(DI.webhooksRepository)
 		private webhooksRepository: WebhooksRepository,
+		private queueService: QueueService,
 	) {
 		this.redisForSub.on('message', this.onMessage);
 	}
@@ -75,6 +77,25 @@ export class UserWebhookService implements OnApplicationShutdown {
 		return query.getMany();
 	}
 
+	/**
+	 * UserWebhook をWebhook配送キューに追加する
+	 * @see QueueService.userWebhookDeliver
+	 */
+	@bindThis
+	public async enqueueUserWebhook(
+		userId: MiUser['id'],
+		type: T,
+		content: UserWebhookPayload,
+	) {
+		const webhooks = await this.getActiveWebhooks()
+			.then(webhooks => webhooks.filter(webhook => webhook.userId === userId && webhook.on.includes(type)));
+		return Promise.all(
+			webhooks.map(webhook => {
+				return this.queueService.userWebhookDeliver(webhook, type, content);
+			}),
+		);
+	}
+
 	@bindThis
 	private async onMessage(_: string, data: string): Promise {
 		const obj = JSON.parse(data);
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 9a2ba72ed31a..fcb750d3bf7d 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -3,8 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { URL } from 'node:url';
-import { toASCII } from 'punycode';
+import { URL, domainToASCII } from 'node:url';
 import { Inject, Injectable } from '@nestjs/common';
 import RE2 from 're2';
 import { DI } from '@/di-symbols.js';
@@ -106,13 +105,13 @@ export class UtilityService {
 
 	@bindThis
 	public toPuny(host: string): string {
-		return toASCII(host.toLowerCase());
+		return domainToASCII(host.toLowerCase());
 	}
 
 	@bindThis
 	public toPunyNullable(host: string | null | undefined): string | null {
 		if (host == null) return null;
-		return toASCII(host.toLowerCase());
+		return domainToASCII(host.toLowerCase());
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index ad53192f18d9..ed75e4f46793 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -189,14 +189,12 @@ export class WebAuthnService {
 	 */
 	@bindThis
 	public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise {
-		const challenge = await this.redisClient.get(`webauthn:challenge:${context}`);
+		const challenge = await this.redisClient.getdel(`webauthn:challenge:${context}`);
 
 		if (!challenge) {
 			throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`);
 		}
 
-		await this.redisClient.del(`webauthn:challenge:${context}`);
-
 		const key = await this.userSecurityKeysRepository.findOneBy({
 			id: response.id,
 		});
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 5617a29bab22..914809506743 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -183,6 +183,9 @@ export class ApRendererService {
 				// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 				url: emoji.publicUrl || emoji.originalUrl,
 			},
+			_misskey_license: {
+				freeText: emoji.license
+			},
 		};
 	}
 
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index b0b35274ea0f..52cc56914013 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
 import { ApRendererService } from './ApRendererService.js';
 import { ApRequestService } from './ApRequestService.js';
 import type { IObject, ICollection, IOrderedCollection } from './type.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 
 export class Resolver {
 	private history: Set;
@@ -66,7 +67,7 @@ export class Resolver {
 		if (isCollectionOrOrderedCollection(collection)) {
 			return collection;
 		} else {
-			throw new Error(`unrecognized collection type: ${collection.type}`);
+			throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`);
 		}
 	}
 
@@ -80,15 +81,15 @@ export class Resolver {
 			// URLs with fragment parts cannot be resolved correctly because
 			// the fragment part does not get transmitted over HTTP(S).
 			// Avoid strange behaviour by not trying to resolve these at all.
-			throw new Error(`cannot resolve URL with fragment: ${value}`);
+			throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`);
 		}
 
 		if (this.history.has(value)) {
-			throw new Error('cannot resolve already resolved one');
+			throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', 'cannot resolve already resolved one');
 		}
 
 		if (this.history.size > this.recursionLimit) {
-			throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
+			throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
 		}
 
 		this.history.add(value);
@@ -99,7 +100,7 @@ export class Resolver {
 		}
 
 		if (!this.utilityService.isFederationAllowedHost(host)) {
-			throw new Error('Instance is blocked');
+			throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked');
 		}
 
 		if (this.config.signToActivityPubGet && !this.user) {
@@ -115,7 +116,7 @@ export class Resolver {
 				!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
 				object['@context'] !== 'https://www.w3.org/ns/activitystreams'
 		) {
-			throw new Error('invalid response');
+			throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
 		}
 
 		// HttpRequestService / ApRequestService have already checked that
@@ -123,11 +124,11 @@ export class Resolver {
 		// object after redirects; here we double-check that no redirects
 		// bounced between hosts
 		if (object.id == null) {
-			throw new Error('invalid AP object: missing id');
+			throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id');
 		}
 
 		if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
-			throw new Error(`invalid AP object ${value}: id ${object.id} has different host`);
+			throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
 		}
 
 		return object;
@@ -136,7 +137,7 @@ export class Resolver {
 	@bindThis
 	private resolveLocal(url: string): Promise {
 		const parsed = this.apDbResolverService.parseUri(url);
-		if (!parsed.local) throw new Error('resolveLocal: not local');
+		if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', 'resolveLocal: not local');
 
 		switch (parsed.type) {
 			case 'notes':
@@ -165,7 +166,7 @@ export class Resolver {
 			case 'follows':
 				return this.followRequestsRepository.findOneBy({ id: parsed.id })
 					.then(async followRequest => {
-						if (followRequest == null) throw new Error('resolveLocal: invalid follow request ID');
+						if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', 'resolveLocal: invalid follow request ID');
 						const [follower, followee] = await Promise.all([
 							this.usersRepository.findOneBy({
 								id: followRequest.followerId,
@@ -177,12 +178,12 @@ export class Resolver {
 							}),
 						]);
 						if (follower == null || followee == null) {
-							throw new Error('resolveLocal: follower or followee does not exist');
+							throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', 'resolveLocal: follower or followee does not exist');
 						}
 						return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
 					});
 			default:
-				throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
+				throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled`);
 		}
 	}
 }
diff --git a/packages/backend/src/core/activitypub/misc/check-against-url.ts b/packages/backend/src/core/activitypub/misc/check-against-url.ts
index 78ba891a2e71..d679bd81804a 100644
--- a/packages/backend/src/core/activitypub/misc/check-against-url.ts
+++ b/packages/backend/src/core/activitypub/misc/check-against-url.ts
@@ -5,13 +5,15 @@
 import type { IObject } from '../type.js';
 
 export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
-	const idOk = activity.id !== undefined && urls.includes(activity.id);
+	const hosts = urls.map(it => new URL(it).host);
+
+	const idOk = activity.id !== undefined && hosts.includes(new URL(activity.id).host);
 
 	// technically `activity.url` could be an `ApObject = IObject |
 	// string | (IObject | string)[]`, but if it's a complicated thing
 	// and the `activity.id` doesn't match, I think we're fine
 	// rejecting the activity
-	const urlOk = typeof(activity.url) === 'string' && urls.includes(activity.url);
+	const urlOk = typeof(activity.url) === 'string' && hosts.includes(new URL(activity.url).host);
 
 	if (!idOk && !urlOk) {
 		throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`);
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index 94cb0785cbb2..6611e4b7f932 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -558,6 +558,11 @@ const extension_context_definition = {
 	'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
 	'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
 	'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
+	'_misskey_license': 'misskey:_misskey_license',
+	'freeText': {
+		'@id': 'misskey:freeText',
+		'@type': 'schema:text',
+	},
 	'isCat': 'misskey:isCat',
 	// vcard
 	vcard: 'http://www.w3.org/2006/vcard/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index eb2e771a38fc..8abacd293fc5 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -154,14 +154,8 @@ export class ApNoteService {
 
 		const url = getOneApHrefNullable(note.url);
 
-		if (url != null) {
-			if (!checkHttps(url)) {
-				throw new Error('unexpected schema of note url: ' + url);
-			}
-
-			if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) {
-				throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
-			}
+		if (url && !checkHttps(url)) {
+			throw new Error('unexpected schema of note url: ' + url);
 		}
 
 		this.logger.info(`Creating the Note: ${note.id}`);
@@ -414,6 +408,8 @@ export class ApNoteService {
 						originalUrl: tag.icon.url,
 						publicUrl: tag.icon.url,
 						updatedAt: new Date(),
+						// _misskey_license が存在しなければ `null`
+						license: (tag._misskey_license?.freeText ?? null)
 					});
 
 					const emoji = await this.emojisRepository.findOneBy({ host, name });
@@ -435,6 +431,8 @@ export class ApNoteService {
 				publicUrl: tag.icon.url,
 				updatedAt: new Date(),
 				aliases: [],
+				// _misskey_license が存在しなければ `null`
+				license: (tag._misskey_license?.freeText ?? null)
 			});
 		}));
 	}
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 8590861ca043..6019906add8e 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -157,8 +157,12 @@ export class ApPersonService implements OnModuleInit {
 		const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
 		if (sharedInboxObject != null) {
 			const sharedInbox = getApId(sharedInboxObject);
-			if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHost(sharedInbox) === expectHost)) {
-				throw new Error('invalid Actor: wrong shared inbox');
+			if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && new URL(sharedInbox).host === expectHost)) {
+				this.logger.warn(`invalid Actor: skipping wrong shared inbox, expected host: ${expectHost}, actual URL: ${sharedInbox}`);
+				x.sharedInbox = undefined;
+				if (x.endpoints?.sharedInbox) {
+					x.endpoints.sharedInbox = undefined;
+				}
 			}
 		}
 
@@ -257,7 +261,7 @@ export class ApPersonService implements OnModuleInit {
 			if (Array.isArray(img)) {
 				img = img.find(item => item && item.url) ?? null;
 			}
-			
+
 			// if we have an explicitly missing image, return an
 			// explicitly-null set of values
 			if ((img == null) || (typeof img === 'object' && img.url == null)) {
@@ -344,14 +348,8 @@ export class ApPersonService implements OnModuleInit {
 			throw new Error('Refusing to create person without id');
 		}
 
-		if (url != null) {
-			if (!checkHttps(url)) {
-				throw new Error('unexpected schema of person url: ' + url);
-			}
-
-			if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) {
-				throw new Error(`person url <> uri host mismatch: ${url} <> ${person.id}`);
-			}
+		if (url && !checkHttps(url)) {
+			throw new Error('unexpected schema of person url: ' + url);
 		}
 
 		// Create user
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 7496315f0970..72732b01dfe8 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -242,6 +242,11 @@ export interface IApEmoji extends IObject {
 	type: 'Emoji';
 	name: string;
 	updated: string;
+	// Misskey拡張。後方互換性のためにoptional。
+	// 将来の拡張性を考慮してobjectにしている
+	_misskey_license?: {
+		freeText: string | null;
+	};
 }
 
 export const isEmoji = (object: IObject): object is IApEmoji =>
diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts
index 79681370a17d..f04c561063d1 100644
--- a/packages/backend/src/core/chart/ChartManagementService.ts
+++ b/packages/backend/src/core/chart/ChartManagementService.ts
@@ -58,9 +58,9 @@ export class ChartManagementService implements OnApplicationShutdown {
 	@bindThis
 	public async start() {
 		// 20分おきにメモリ情報をDBに書き込み
-		this.saveIntervalId = setInterval(() => {
+		this.saveIntervalId = setInterval(async () => {
 			for (const chart of this.charts) {
-				chart.save();
+				await chart.save();
 			}
 		}, 1000 * 60 * 20);
 	}
@@ -69,9 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown {
 	public async dispose(): Promise {
 		clearInterval(this.saveIntervalId);
 		if (process.env.NODE_ENV !== 'test') {
-			await Promise.all(
-				this.charts.map(chart => chart.save()),
-			);
+			for (const chart of this.charts) {
+				await chart.save();
+			}
 		}
 	}
 
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 841bd731c0cb..490d3f2511cc 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -4,10 +4,10 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
+import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { EmojisRepository } from '@/models/_.js';
+import type { EmojisRepository, MiRole, RolesRepository } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
-import type { } from '@/models/Blocking.js';
 import type { MiEmoji } from '@/models/Emoji.js';
 import { bindThis } from '@/decorators.js';
 
@@ -16,6 +16,8 @@ export class EmojiEntityService {
 	constructor(
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
+		@Inject(DI.rolesRepository)
+		private rolesRepository: RolesRepository,
 	) {
 	}
 
@@ -68,8 +70,90 @@ export class EmojiEntityService {
 	@bindThis
 	public packDetailedMany(
 		emojis: any[],
-	) {
+	): Promise[]> {
 		return Promise.all(emojis.map(x => this.packDetailed(x)));
 	}
+
+	@bindThis
+	public async packDetailedAdmin(
+		src: MiEmoji['id'] | MiEmoji,
+		hint?: {
+			roles?: Map
+		},
+	): Promise> {
+		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
+
+		const roles = Array.of();
+		if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0) {
+			if (hint?.roles) {
+				const hintRoles = hint.roles;
+				roles.push(
+					...emoji.roleIdsThatCanBeUsedThisEmojiAsReaction
+						.filter(x => hintRoles.has(x))
+						.map(x => hintRoles.get(x)!),
+				);
+			} else {
+				roles.push(
+					...await this.rolesRepository.findBy({ id: In(emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) }),
+				);
+			}
+
+			roles.sort((a, b) => {
+				if (a.displayOrder !== b.displayOrder) {
+					return b.displayOrder - a.displayOrder;
+				}
+
+				return a.id.localeCompare(b.id);
+			});
+		}
+
+		return {
+			id: emoji.id,
+			updatedAt: emoji.updatedAt?.toISOString() ?? null,
+			name: emoji.name,
+			host: emoji.host,
+			uri: emoji.uri,
+			type: emoji.type,
+			aliases: emoji.aliases,
+			category: emoji.category,
+			publicUrl: emoji.publicUrl,
+			originalUrl: emoji.originalUrl,
+			license: emoji.license,
+			localOnly: emoji.localOnly,
+			isSensitive: emoji.isSensitive,
+			roleIdsThatCanBeUsedThisEmojiAsReaction: roles.map(it => ({ id: it.id, name: it.name })),
+		};
+	}
+
+	@bindThis
+	public async packDetailedAdminMany(
+		emojis: MiEmoji['id'][] | MiEmoji[],
+		hint?: {
+			roles?: Map
+		},
+	): Promise[]> {
+		// IDのみの要素をピックアップし、DBからレコードを取り出して他の値を補完する
+		const emojiEntities = emojis.filter(x => typeof x === 'object') as MiEmoji[];
+		const emojiIdOnlyList = emojis.filter(x => typeof x === 'string') as string[];
+		if (emojiIdOnlyList.length > 0) {
+			emojiEntities.push(...await this.emojisRepository.findBy({ id: In(emojiIdOnlyList) }));
+		}
+
+		// 特定ロール専用の絵文字である場合、そのロール情報をあらかじめまとめて取得しておく(pack側で都度取得も出来るが負荷が高いので)
+		let hintRoles: Map;
+		if (hint?.roles) {
+			hintRoles = hint.roles;
+		} else {
+			const roles = Array.of();
+			const roleIds = [...new Set(emojiEntities.flatMap(x => x.roleIdsThatCanBeUsedThisEmojiAsReaction))];
+			if (roleIds.length > 0) {
+				roles.push(...await this.rolesRepository.findBy({ id: In(roleIds) }));
+			}
+
+			hintRoles = new Map(roles.map(x => [x.id, x]));
+		}
+
+		return Promise.all(emojis.map(x => this.packDetailedAdmin(x, { roles: hintRoles })));
+	}
 }
 
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index 409dca34263b..ec0b5360f4a2 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -132,6 +132,7 @@ export class MetaEntityService {
 			enableUrlPreview: instance.urlPreviewEnabled,
 			noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
 			maxFileSize: this.config.maxFileSize,
+			federation: this.meta.federation,
 		};
 
 		return packed;
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 96cc6b028ec0..97f1c3d739e3 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -102,8 +102,7 @@ export class NoteEntityService implements OnModuleInit {
 	}
 
 	@bindThis
-	private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise {
-		// FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある)
+	private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] {
 		if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
 			const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
 			if ((followersOnlyBefore != null)
@@ -115,7 +114,11 @@ export class NoteEntityService implements OnModuleInit {
 				packedNote.visibility = 'followers';
 			}
 		}
+		return packedNote.visibility;
+	}
 
+	@bindThis
+	private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise {
 		if (meId === packedNote.userId) return;
 
 		// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
@@ -458,6 +461,8 @@ export class NoteEntityService implements OnModuleInit {
 			} : {}),
 		});
 
+		this.treatVisibility(packed);
+
 		if (!opts.skipHide) {
 			await this.hideNote(packed, meId);
 		}
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 040e36228ccf..f612591eda03 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -33,7 +33,11 @@ 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 { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
-import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
+import {
+	packedEmojiDetailedAdminSchema,
+	packedEmojiDetailedSchema,
+	packedEmojiSimpleSchema,
+} from '@/models/json-schema/emoji.js';
 import { packedFlashSchema } from '@/models/json-schema/flash.js';
 import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
 import { packedSigninSchema } from '@/models/json-schema/signin.js';
@@ -95,6 +99,7 @@ export const refs = {
 	GalleryPost: packedGalleryPostSchema,
 	EmojiSimple: packedEmojiSimpleSchema,
 	EmojiDetailed: packedEmojiDetailedSchema,
+	EmojiDetailedAdmin: packedEmojiDetailedAdminSchema,
 	Flash: packedFlashSchema,
 	Signin: packedSigninSchema,
 	RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 62686ad5ae62..3cd263fa377d 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -104,3 +104,86 @@ export const packedEmojiDetailedSchema = {
 		},
 	},
 } as const;
+
+export const packedEmojiDetailedAdminSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			format: 'id',
+			optional: false, nullable: false,
+		},
+		updatedAt: {
+			type: 'string',
+			format: 'date-time',
+			optional: false, nullable: true,
+		},
+		name: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		host: {
+			type: 'string',
+			optional: false, nullable: true,
+			description: 'The local host is represented with `null`.',
+		},
+		publicUrl: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		originalUrl: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		uri: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		type: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		aliases: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				format: 'id',
+				optional: false, nullable: false,
+			},
+		},
+		category: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		license: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		localOnly: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isSensitive: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		roleIdsThatCanBeUsedThisEmojiAsReaction: {
+			type: 'array',
+			items: {
+				type: 'object',
+				properties: {
+					id: {
+						type: 'string',
+						format: 'misskey:id',
+						optional: false, nullable: false,
+					},
+					name: {
+						type: 'string',
+						optional: false, nullable: false,
+					},
+				},
+			},
+		},
+	},
+} as const;
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index e3fd63464a81..e7ae2ee8e560 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -261,6 +261,11 @@ export const packedMetaLiteSchema = {
 			type: 'number',
 			optional: false, nullable: false,
 		},
+		federation: {
+			type: 'string',
+			enum: ['all', 'specified', 'none'],
+			optional: false, nullable: false,
+		},
 	},
 } as const;
 
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 251a03c303a7..d09240eba1c9 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -89,27 +89,65 @@ export const dbLogger = new MisskeyLogger('db');
 
 const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
 
+export type LoggerProps = {
+	disableQueryTruncation?: boolean;
+	enableQueryParamLogging?: 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;
+}
+
+function stringifyParameter(param: any) {
+	if (param instanceof Date) {
+		return param.toISOString();
+	} else {
+		return param;
+	}
+}
+
 class MyCustomLogger implements Logger {
+	constructor(private props: LoggerProps = {}) {
+	}
+
+	@bindThis
+	private transformQueryLog(sql: string) {
+		let modded = sql;
+		if (!this.props.disableQueryTruncation) {
+			modded = truncateSql(modded);
+		}
+
+		return highlightSql(modded);
+	}
+
 	@bindThis
-	private highlight(sql: string) {
-		return highlight.highlight(sql, {
-			language: 'sql', ignoreIllegals: true,
-		});
+	private transformParameters(parameters?: any[]) {
+		if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) {
+			return parameters.map(stringifyParameter);
+		}
+
+		return undefined;
 	}
 
 	@bindThis
 	public logQuery(query: string, parameters?: any[]) {
-		sqlLogger.info(this.highlight(query).substring(0, 100));
+		sqlLogger.info(this.transformQueryLog(query), this.transformParameters(parameters));
 	}
 
 	@bindThis
 	public logQueryError(error: string, query: string, parameters?: any[]) {
-		sqlLogger.error(this.highlight(query));
+		sqlLogger.error(this.transformQueryLog(query), this.transformParameters(parameters));
 	}
 
 	@bindThis
 	public logQuerySlow(time: number, query: string, parameters?: any[]) {
-		sqlLogger.warn(this.highlight(query));
+		sqlLogger.warn(this.transformQueryLog(query), this.transformParameters(parameters));
 	}
 
 	@bindThis
@@ -247,7 +285,12 @@ export function createPostgresDataSource(config: Config) {
 			},
 		} : false,
 		logging: log,
-		logger: log ? new MyCustomLogger() : undefined,
+		logger: log
+			? new MyCustomLogger({
+				disableQueryTruncation: config.logging?.sql?.disableQueryTruncation,
+				enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging,
+			})
+			: undefined,
 		maxQueryExecutionTime: 300,
 		entities: entities,
 		migrations: ['../../migration/*.js'],
diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
index 87183cb34218..2e84430e72a4 100644
--- a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -231,15 +231,10 @@ export class CheckModeratorsActivityProcessorService {
 
 		// -- SystemWebhook
 
-		const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
-			.then(it => it.filter(it => it.on.includes('inactiveModeratorsWarning')));
-		for (const systemWebhook of systemWebhooks) {
-			this.systemWebhookService.enqueueSystemWebhook(
-				systemWebhook,
-				'inactiveModeratorsWarning',
-				{ remainingTime: remainingTime },
-			);
-		}
+		return this.systemWebhookService.enqueueSystemWebhook(
+			'inactiveModeratorsWarning',
+			{ remainingTime: remainingTime },
+		);
 	}
 
 	@bindThis
@@ -269,15 +264,10 @@ export class CheckModeratorsActivityProcessorService {
 
 		// -- SystemWebhook
 
-		const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
-			.then(it => it.filter(it => it.on.includes('inactiveModeratorsInvitationOnlyChanged')));
-		for (const systemWebhook of systemWebhooks) {
-			this.systemWebhookService.enqueueSystemWebhook(
-				systemWebhook,
-				'inactiveModeratorsInvitationOnlyChanged',
-				{},
-			);
-		}
+		return this.systemWebhookService.enqueueSystemWebhook(
+			'inactiveModeratorsInvitationOnlyChanged',
+			{},
+		);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
index 110468801ce2..8c5faa8d0711 100644
--- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
@@ -48,20 +48,19 @@ export class CleanChartsProcessorService {
 	public async process(): Promise {
 		this.logger.info('Clean charts...');
 
-		await Promise.all([
-			this.federationChart.clean(),
-			this.notesChart.clean(),
-			this.usersChart.clean(),
-			this.activeUsersChart.clean(),
-			this.instanceChart.clean(),
-			this.perUserNotesChart.clean(),
-			this.perUserPvChart.clean(),
-			this.driveChart.clean(),
-			this.perUserReactionsChart.clean(),
-			this.perUserFollowingChart.clean(),
-			this.perUserDriveChart.clean(),
-			this.apRequestChart.clean(),
-		]);
+		// DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する
+		await this.federationChart.clean();
+		await this.notesChart.clean();
+		await this.usersChart.clean();
+		await this.activeUsersChart.clean();
+		await this.instanceChart.clean();
+		await this.perUserNotesChart.clean();
+		await this.perUserPvChart.clean();
+		await this.driveChart.clean();
+		await this.perUserReactionsChart.clean();
+		await this.perUserFollowingChart.clean();
+		await this.perUserDriveChart.clean();
+		await this.apRequestChart.clean();
 
 		this.logger.succ('All charts successfully cleaned.');
 	}
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index 9e1b8fee7034..725e1c8ba2fe 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -87,6 +87,7 @@ export class ImportCustomEmojisProcessorService {
 				await this.emojisRepository.delete({
 					name: emojiInfo.name,
 				});
+
 				try {
 					const driveFile = await this.driveService.addFile({
 						user: null,
@@ -95,11 +96,13 @@ export class ImportCustomEmojisProcessorService {
 						force: true,
 					});
 					await this.customEmojiService.add({
+						originalUrl: driveFile.url,
+						publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+						fileType: driveFile.webpublicType ?? driveFile.type,
 						name: emojiInfo.name,
 						category: emojiInfo.category,
 						host: null,
 						aliases: emojiInfo.aliases,
-						driveFile,
 						license: emojiInfo.license,
 						isSensitive: emojiInfo.isSensitive,
 						localOnly: emojiInfo.localOnly,
diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
index 570cdf9a7504..0c47fdedb3b0 100644
--- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
@@ -29,13 +29,12 @@ export class ResyncChartsProcessorService {
 	public async process(): Promise {
 		this.logger.info('Resync charts...');
 
+		// DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する
 		// TODO: ユーザーごとのチャートも更新する
 		// TODO: インスタンスごとのチャートも更新する
-		await Promise.all([
-			this.driveChart.resync(),
-			this.notesChart.resync(),
-			this.usersChart.resync(),
-		]);
+		await this.driveChart.resync();
+		await this.notesChart.resync();
+		await this.usersChart.resync();
 
 		this.logger.succ('All charts successfully resynced.');
 	}
diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
index 93ec34162db4..fc8856a2710e 100644
--- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
@@ -48,20 +48,19 @@ export class TickChartsProcessorService {
 	public async process(): Promise {
 		this.logger.info('Tick charts...');
 
-		await Promise.all([
-			this.federationChart.tick(false),
-			this.notesChart.tick(false),
-			this.usersChart.tick(false),
-			this.activeUsersChart.tick(false),
-			this.instanceChart.tick(false),
-			this.perUserNotesChart.tick(false),
-			this.perUserPvChart.tick(false),
-			this.driveChart.tick(false),
-			this.perUserReactionsChart.tick(false),
-			this.perUserFollowingChart.tick(false),
-			this.perUserDriveChart.tick(false),
-			this.apRequestChart.tick(false),
-		]);
+		// DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する
+		await this.federationChart.tick(false);
+		await this.notesChart.tick(false);
+		await this.usersChart.tick(false);
+		await this.activeUsersChart.tick(false);
+		await this.instanceChart.tick(false);
+		await this.perUserNotesChart.tick(false);
+		await this.perUserPvChart.tick(false);
+		await this.driveChart.tick(false);
+		await this.perUserReactionsChart.tick(false);
+		await this.perUserFollowingChart.tick(false);
+		await this.perUserDriveChart.tick(false);
+		await this.apRequestChart.tick(false);
 
 		this.logger.succ('All charts successfully ticked.');
 	}
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index a4077a0547ea..5db919a1497c 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -6,9 +6,12 @@
 import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiNote } from '@/models/Note.js';
+import type { SystemWebhookEventType } from '@/models/SystemWebhook.js';
 import type { MiUser } from '@/models/User.js';
-import type { MiWebhook } from '@/models/Webhook.js';
+import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
 import type { IActivity } from '@/core/activitypub/type.js';
+import type { SystemWebhookPayload } from '@/core/SystemWebhookService.js';
+import type { UserWebhookPayload } from '@/core/UserWebhookService.js';
 import type httpSignature from '@peertube/http-signature';
 
 export type DeliverJobData = {
@@ -106,9 +109,9 @@ export type EndedPollNotificationJobData = {
 	noteId: MiNote['id'];
 };
 
-export type SystemWebhookDeliverJobData = {
-	type: string;
-	content: unknown;
+export type SystemWebhookDeliverJobData = {
+	type: T;
+	content: SystemWebhookPayload;
 	webhookId: MiWebhook['id'];
 	to: string;
 	secret: string;
@@ -116,9 +119,9 @@ export type SystemWebhookDeliverJobData = {
 	eventId: string;
 };
 
-export type UserWebhookDeliverJobData = {
-	type: string;
-	content: unknown;
+export type UserWebhookDeliverJobData = {
+	type: T;
+	content: UserWebhookPayload;
 	webhookId: MiWebhook['id'];
 	userId: MiUser['id'];
 	to: string;
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index f34f6583d359..8c4b13a40a88 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -519,8 +519,8 @@ export class ActivityPubServerService {
 			},
 			deriveConstraint(request: IncomingMessage) {
 				const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]);
-				const isAp = typeof accepted === 'string' && !accepted.match(/html/);
-				return isAp ? 'ap' : 'html';
+				if (accepted === false) return null;
+				return accepted !== 'html' ? 'ap' : 'html';
 			},
 		});
 
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 5bb194313d2c..9cfb2f0ac0a1 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -6,778 +6,13 @@
 import { Module } from '@nestjs/common';
 
 import { CoreModule } from '@/core/CoreModule.js';
-import * as ep___admin_abuseReport_notificationRecipient_list from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
-import * as ep___admin_abuseReport_notificationRecipient_show from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
-import * as ep___admin_abuseReport_notificationRecipient_create from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
-import * as ep___admin_abuseReport_notificationRecipient_update from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
-import * as ep___admin_abuseReport_notificationRecipient_delete from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
-import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
-import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
-import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
-import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
-import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
-import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
-import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
-import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
-import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
-import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
-import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
-import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
-import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
-import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
-import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
-import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
-import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
-import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
-import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
-import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
-import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
-import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
-import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
-import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
-import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
-import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
-import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
-import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
-import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
-import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
-import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
-import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
-import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
-import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
-import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
-import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
-import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
-import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
-import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
-import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
-import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
-import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
-import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
-import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
-import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
-import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
-import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
-import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
-import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
-import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
-import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
-import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
-import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
-import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
-import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
-import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
-import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
-import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
-import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
-import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
-import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
-import * as ep___admin_showUser from './endpoints/admin/show-user.js';
-import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
-import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
-import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
-import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
-import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
-import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
-import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
-import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
-import * as ep___admin_roles_list from './endpoints/admin/roles/list.js';
-import * as ep___admin_roles_show from './endpoints/admin/roles/show.js';
-import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
-import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
-import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
-import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
-import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
-import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
-import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
-import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
-import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
-import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
-import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
-import * as ep___announcements from './endpoints/announcements.js';
-import * as ep___announcements_show from './endpoints/announcements/show.js';
-import * as ep___antennas_create from './endpoints/antennas/create.js';
-import * as ep___antennas_delete from './endpoints/antennas/delete.js';
-import * as ep___antennas_list from './endpoints/antennas/list.js';
-import * as ep___antennas_notes from './endpoints/antennas/notes.js';
-import * as ep___antennas_show from './endpoints/antennas/show.js';
-import * as ep___antennas_update from './endpoints/antennas/update.js';
-import * as ep___ap_get from './endpoints/ap/get.js';
-import * as ep___ap_show from './endpoints/ap/show.js';
-import * as ep___app_create from './endpoints/app/create.js';
-import * as ep___app_show from './endpoints/app/show.js';
-import * as ep___auth_accept from './endpoints/auth/accept.js';
-import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
-import * as ep___auth_session_show from './endpoints/auth/session/show.js';
-import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
-import * as ep___blocking_create from './endpoints/blocking/create.js';
-import * as ep___blocking_delete from './endpoints/blocking/delete.js';
-import * as ep___blocking_list from './endpoints/blocking/list.js';
-import * as ep___channels_create from './endpoints/channels/create.js';
-import * as ep___channels_featured from './endpoints/channels/featured.js';
-import * as ep___channels_follow from './endpoints/channels/follow.js';
-import * as ep___channels_followed from './endpoints/channels/followed.js';
-import * as ep___channels_owned from './endpoints/channels/owned.js';
-import * as ep___channels_show from './endpoints/channels/show.js';
-import * as ep___channels_timeline from './endpoints/channels/timeline.js';
-import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
-import * as ep___channels_update from './endpoints/channels/update.js';
-import * as ep___channels_favorite from './endpoints/channels/favorite.js';
-import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
-import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
-import * as ep___channels_search from './endpoints/channels/search.js';
-import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
-import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
-import * as ep___charts_drive from './endpoints/charts/drive.js';
-import * as ep___charts_federation from './endpoints/charts/federation.js';
-import * as ep___charts_instance from './endpoints/charts/instance.js';
-import * as ep___charts_notes from './endpoints/charts/notes.js';
-import * as ep___charts_user_drive from './endpoints/charts/user/drive.js';
-import * as ep___charts_user_following from './endpoints/charts/user/following.js';
-import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
-import * as ep___charts_user_pv from './endpoints/charts/user/pv.js';
-import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
-import * as ep___charts_users from './endpoints/charts/users.js';
-import * as ep___clips_addNote from './endpoints/clips/add-note.js';
-import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
-import * as ep___clips_create from './endpoints/clips/create.js';
-import * as ep___clips_delete from './endpoints/clips/delete.js';
-import * as ep___clips_list from './endpoints/clips/list.js';
-import * as ep___clips_notes from './endpoints/clips/notes.js';
-import * as ep___clips_show from './endpoints/clips/show.js';
-import * as ep___clips_update from './endpoints/clips/update.js';
-import * as ep___clips_favorite from './endpoints/clips/favorite.js';
-import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
-import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
-import * as ep___drive from './endpoints/drive.js';
-import * as ep___drive_files from './endpoints/drive/files.js';
-import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
-import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js';
-import * as ep___drive_files_create from './endpoints/drive/files/create.js';
-import * as ep___drive_files_delete from './endpoints/drive/files/delete.js';
-import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js';
-import * as ep___drive_files_find from './endpoints/drive/files/find.js';
-import * as ep___drive_files_show from './endpoints/drive/files/show.js';
-import * as ep___drive_files_update from './endpoints/drive/files/update.js';
-import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js';
-import * as ep___drive_folders from './endpoints/drive/folders.js';
-import * as ep___drive_folders_create from './endpoints/drive/folders/create.js';
-import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js';
-import * as ep___drive_folders_find from './endpoints/drive/folders/find.js';
-import * as ep___drive_folders_show from './endpoints/drive/folders/show.js';
-import * as ep___drive_folders_update from './endpoints/drive/folders/update.js';
-import * as ep___drive_stream from './endpoints/drive/stream.js';
-import * as ep___emailAddress_available from './endpoints/email-address/available.js';
-import * as ep___endpoint from './endpoints/endpoint.js';
-import * as ep___endpoints from './endpoints/endpoints.js';
-import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js';
-import * as ep___federation_followers from './endpoints/federation/followers.js';
-import * as ep___federation_following from './endpoints/federation/following.js';
-import * as ep___federation_instances from './endpoints/federation/instances.js';
-import * as ep___federation_showInstance from './endpoints/federation/show-instance.js';
-import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js';
-import * as ep___federation_users from './endpoints/federation/users.js';
-import * as ep___federation_stats from './endpoints/federation/stats.js';
-import * as ep___following_create from './endpoints/following/create.js';
-import * as ep___following_delete from './endpoints/following/delete.js';
-import * as ep___following_update from './endpoints/following/update.js';
-import * as ep___following_update_all from './endpoints/following/update-all.js';
-import * as ep___following_invalidate from './endpoints/following/invalidate.js';
-import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
-import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
-import * as ep___following_requests_list from './endpoints/following/requests/list.js';
-import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
-import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
-import * as ep___gallery_featured from './endpoints/gallery/featured.js';
-import * as ep___gallery_popular from './endpoints/gallery/popular.js';
-import * as ep___gallery_posts from './endpoints/gallery/posts.js';
-import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js';
-import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js';
-import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js';
-import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
-import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
-import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
-import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
-import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
-import * as ep___hashtags_list from './endpoints/hashtags/list.js';
-import * as ep___hashtags_search from './endpoints/hashtags/search.js';
-import * as ep___hashtags_show from './endpoints/hashtags/show.js';
-import * as ep___hashtags_trend from './endpoints/hashtags/trend.js';
-import * as ep___hashtags_users from './endpoints/hashtags/users.js';
-import * as ep___i from './endpoints/i.js';
-import * as ep___i_2fa_done from './endpoints/i/2fa/done.js';
-import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
-import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
-import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
-import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
-import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
-import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
-import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
-import * as ep___i_apps from './endpoints/i/apps.js';
-import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js';
-import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js';
-import * as ep___i_changePassword from './endpoints/i/change-password.js';
-import * as ep___i_deleteAccount from './endpoints/i/delete-account.js';
-import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
-import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
-import * as ep___i_exportMute from './endpoints/i/export-mute.js';
-import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
-import * as ep___i_exportClips from './endpoints/i/export-clips.js';
-import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
-import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
-import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
-import * as ep___i_favorites from './endpoints/i/favorites.js';
-import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
-import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
-import * as ep___i_importFollowing from './endpoints/i/import-following.js';
-import * as ep___i_importMuting from './endpoints/i/import-muting.js';
-import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
-import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
-import * as ep___i_notifications from './endpoints/i/notifications.js';
-import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js';
-import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
-import * as ep___i_pages from './endpoints/i/pages.js';
-import * as ep___i_pin from './endpoints/i/pin.js';
-import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
-import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
-import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
-import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js';
-import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js';
-import * as ep___i_registry_get from './endpoints/i/registry/get.js';
-import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js';
-import * as ep___i_registry_keys from './endpoints/i/registry/keys.js';
-import * as ep___i_registry_remove from './endpoints/i/registry/remove.js';
-import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js';
-import * as ep___i_registry_set from './endpoints/i/registry/set.js';
-import * as ep___i_revokeToken from './endpoints/i/revoke-token.js';
-import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
-import * as ep___i_unpin from './endpoints/i/unpin.js';
-import * as ep___i_updateEmail from './endpoints/i/update-email.js';
-import * as ep___i_update from './endpoints/i/update.js';
-import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
-import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
-import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
-import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
-import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
-import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
-import * as ep___invite_create from './endpoints/invite/create.js';
-import * as ep___invite_delete from './endpoints/invite/delete.js';
-import * as ep___invite_list from './endpoints/invite/list.js';
-import * as ep___invite_limit from './endpoints/invite/limit.js';
-import * as ep___meta from './endpoints/meta.js';
-import * as ep___emojis from './endpoints/emojis.js';
-import * as ep___emoji from './endpoints/emoji.js';
-import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
-import * as ep___mute_create from './endpoints/mute/create.js';
-import * as ep___mute_delete from './endpoints/mute/delete.js';
-import * as ep___mute_list from './endpoints/mute/list.js';
-import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
-import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
-import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
-import * as ep___my_apps from './endpoints/my/apps.js';
-import * as ep___notes from './endpoints/notes.js';
-import * as ep___notes_children from './endpoints/notes/children.js';
-import * as ep___notes_clips from './endpoints/notes/clips.js';
-import * as ep___notes_conversation from './endpoints/notes/conversation.js';
-import * as ep___notes_create from './endpoints/notes/create.js';
-import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
-import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
-import * as ep___notes_featured from './endpoints/notes/featured.js';
-import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
-import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
-import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
-import * as ep___notes_mentions from './endpoints/notes/mentions.js';
-import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
-import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
-import * as ep___notes_reactions from './endpoints/notes/reactions.js';
-import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
-import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
-import * as ep___notes_renotes from './endpoints/notes/renotes.js';
-import * as ep___notes_replies from './endpoints/notes/replies.js';
-import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
-import * as ep___notes_search from './endpoints/notes/search.js';
-import * as ep___notes_show from './endpoints/notes/show.js';
-import * as ep___notes_state from './endpoints/notes/state.js';
-import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js';
-import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
-import * as ep___notes_timeline from './endpoints/notes/timeline.js';
-import * as ep___notes_translate from './endpoints/notes/translate.js';
-import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
-import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
-import * as ep___notifications_create from './endpoints/notifications/create.js';
-import * as ep___notifications_flush from './endpoints/notifications/flush.js';
-import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
-import * as ep___pagePush from './endpoints/page-push.js';
-import * as ep___pages_create from './endpoints/pages/create.js';
-import * as ep___pages_delete from './endpoints/pages/delete.js';
-import * as ep___pages_featured from './endpoints/pages/featured.js';
-import * as ep___pages_like from './endpoints/pages/like.js';
-import * as ep___pages_show from './endpoints/pages/show.js';
-import * as ep___pages_unlike from './endpoints/pages/unlike.js';
-import * as ep___pages_update from './endpoints/pages/update.js';
-import * as ep___flash_create from './endpoints/flash/create.js';
-import * as ep___flash_delete from './endpoints/flash/delete.js';
-import * as ep___flash_featured from './endpoints/flash/featured.js';
-import * as ep___flash_like from './endpoints/flash/like.js';
-import * as ep___flash_show from './endpoints/flash/show.js';
-import * as ep___flash_unlike from './endpoints/flash/unlike.js';
-import * as ep___flash_update from './endpoints/flash/update.js';
-import * as ep___flash_my from './endpoints/flash/my.js';
-import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
-import * as ep___ping from './endpoints/ping.js';
-import * as ep___pinnedUsers from './endpoints/pinned-users.js';
-import * as ep___promo_read from './endpoints/promo/read.js';
-import * as ep___roles_list from './endpoints/roles/list.js';
-import * as ep___roles_show from './endpoints/roles/show.js';
-import * as ep___roles_users from './endpoints/roles/users.js';
-import * as ep___roles_notes from './endpoints/roles/notes.js';
-import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
-import * as ep___resetDb from './endpoints/reset-db.js';
-import * as ep___resetPassword from './endpoints/reset-password.js';
-import * as ep___serverInfo from './endpoints/server-info.js';
-import * as ep___stats from './endpoints/stats.js';
-import * as ep___sw_show_registration from './endpoints/sw/show-registration.js';
-import * as ep___sw_update_registration from './endpoints/sw/update-registration.js';
-import * as ep___sw_register from './endpoints/sw/register.js';
-import * as ep___sw_unregister from './endpoints/sw/unregister.js';
-import * as ep___test from './endpoints/test.js';
-import * as ep___username_available from './endpoints/username/available.js';
-import * as ep___users from './endpoints/users.js';
-import * as ep___users_clips from './endpoints/users/clips.js';
-import * as ep___users_followers from './endpoints/users/followers.js';
-import * as ep___users_following from './endpoints/users/following.js';
-import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
-import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
-import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
-import * as ep___users_lists_create from './endpoints/users/lists/create.js';
-import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
-import * as ep___users_lists_list from './endpoints/users/lists/list.js';
-import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
-import * as ep___users_lists_push from './endpoints/users/lists/push.js';
-import * as ep___users_lists_show from './endpoints/users/lists/show.js';
-import * as ep___users_lists_update from './endpoints/users/lists/update.js';
-import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
-import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
-import * as ep___users_notes from './endpoints/users/notes.js';
-import * as ep___users_pages from './endpoints/users/pages.js';
-import * as ep___users_flashs from './endpoints/users/flashs.js';
-import * as ep___users_reactions from './endpoints/users/reactions.js';
-import * as ep___users_recommendation from './endpoints/users/recommendation.js';
-import * as ep___users_relation from './endpoints/users/relation.js';
-import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
-import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
-import * as ep___users_search from './endpoints/users/search.js';
-import * as ep___users_show from './endpoints/users/show.js';
-import * as ep___users_achievements from './endpoints/users/achievements.js';
-import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
-import * as ep___fetchRss from './endpoints/fetch-rss.js';
-import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
-import * as ep___retention from './endpoints/retention.js';
-import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
-import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
-import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
-import * as ep___reversi_games from './endpoints/reversi/games.js';
-import * as ep___reversi_match from './endpoints/reversi/match.js';
-import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
-import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
-import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
-import * as ep___reversi_verify from './endpoints/reversi/verify.js';
+import * as endpointsObject from './endpoint-list.js';
 import { GetterService } from './GetterService.js';
 import { ApiLoggerService } from './ApiLoggerService.js';
 import type { Provider } from '@nestjs/common';
 
-const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
-const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
-const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default };
-const $admin_abuseReport_notificationRecipient_show: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/show', useClass: ep___admin_abuseReport_notificationRecipient_show.default };
-const $admin_abuseReport_notificationRecipient_create: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/create', useClass: ep___admin_abuseReport_notificationRecipient_create.default };
-const $admin_abuseReport_notificationRecipient_update: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/update', useClass: ep___admin_abuseReport_notificationRecipient_update.default };
-const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/delete', useClass: ep___admin_abuseReport_notificationRecipient_delete.default };
-const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
-const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
-const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
-const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default };
-const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default };
-const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default };
-const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep___admin_ad_update.default };
-const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default };
-const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
-const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
-const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
-const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
-const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
-const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
-const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
-const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
-const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
-const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
-const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
-const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
-const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
-const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
-const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
-const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
-const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
-const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
-const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
-const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default };
-const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default };
-const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default };
-const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
-const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
-const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
-const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
-const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
-const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
-const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
-const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
-const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federation/update-instance', useClass: ep___admin_federation_updateInstance.default };
-const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default };
-const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default };
-const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default };
-const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default };
-const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default };
-const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default };
-const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
-const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
-const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default };
-const $admin_queue_promote: Provider = { provide: 'ep:admin/queue/promote', useClass: ep___admin_queue_promote.default };
-const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default };
-const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default };
-const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default };
-const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default };
-const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default };
-const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default };
-const $admin_forwardAbuseUserReport: Provider = { provide: 'ep:admin/forward-abuse-user-report', useClass: ep___admin_forwardAbuseUserReport.default };
-const $admin_updateAbuseUserReport: Provider = { provide: 'ep:admin/update-abuse-user-report', useClass: ep___admin_updateAbuseUserReport.default };
-const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
-const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
-const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default };
-const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default };
-const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
-const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
-const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
-const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
-const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
-const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default };
-const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default };
-const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default };
-const $admin_roles_list: Provider = { provide: 'ep:admin/roles/list', useClass: ep___admin_roles_list.default };
-const $admin_roles_show: Provider = { provide: 'ep:admin/roles/show', useClass: ep___admin_roles_show.default };
-const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useClass: ep___admin_roles_update.default };
-const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default };
-const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default };
-const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default };
-const $admin_roles_users: Provider = { provide: 'ep:admin/roles/users', useClass: ep___admin_roles_users.default };
-const $admin_systemWebhook_create: Provider = { provide: 'ep:admin/system-webhook/create', useClass: ep___admin_systemWebhook_create.default };
-const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhook/delete', useClass: ep___admin_systemWebhook_delete.default };
-const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
-const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
-const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
-const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default };
-const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
-const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
-const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
-const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default };
-const $antennas_list: Provider = { provide: 'ep:antennas/list', useClass: ep___antennas_list.default };
-const $antennas_notes: Provider = { provide: 'ep:antennas/notes', useClass: ep___antennas_notes.default };
-const $antennas_show: Provider = { provide: 'ep:antennas/show', useClass: ep___antennas_show.default };
-const $antennas_update: Provider = { provide: 'ep:antennas/update', useClass: ep___antennas_update.default };
-const $ap_get: Provider = { provide: 'ep:ap/get', useClass: ep___ap_get.default };
-const $ap_show: Provider = { provide: 'ep:ap/show', useClass: ep___ap_show.default };
-const $app_create: Provider = { provide: 'ep:app/create', useClass: ep___app_create.default };
-const $app_show: Provider = { provide: 'ep:app/show', useClass: ep___app_show.default };
-const $auth_accept: Provider = { provide: 'ep:auth/accept', useClass: ep___auth_accept.default };
-const $auth_session_generate: Provider = { provide: 'ep:auth/session/generate', useClass: ep___auth_session_generate.default };
-const $auth_session_show: Provider = { provide: 'ep:auth/session/show', useClass: ep___auth_session_show.default };
-const $auth_session_userkey: Provider = { provide: 'ep:auth/session/userkey', useClass: ep___auth_session_userkey.default };
-const $blocking_create: Provider = { provide: 'ep:blocking/create', useClass: ep___blocking_create.default };
-const $blocking_delete: Provider = { provide: 'ep:blocking/delete', useClass: ep___blocking_delete.default };
-const $blocking_list: Provider = { provide: 'ep:blocking/list', useClass: ep___blocking_list.default };
-const $channels_create: Provider = { provide: 'ep:channels/create', useClass: ep___channels_create.default };
-const $channels_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default };
-const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.default };
-const $channels_followed: Provider = { provide: 'ep:channels/followed', useClass: ep___channels_followed.default };
-const $channels_owned: Provider = { provide: 'ep:channels/owned', useClass: ep___channels_owned.default };
-const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___channels_show.default };
-const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default };
-const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default };
-const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default };
-const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default };
-const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default };
-const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default };
-const $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.default };
-const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default };
-const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default };
-const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default };
-const $charts_federation: Provider = { provide: 'ep:charts/federation', useClass: ep___charts_federation.default };
-const $charts_instance: Provider = { provide: 'ep:charts/instance', useClass: ep___charts_instance.default };
-const $charts_notes: Provider = { provide: 'ep:charts/notes', useClass: ep___charts_notes.default };
-const $charts_user_drive: Provider = { provide: 'ep:charts/user/drive', useClass: ep___charts_user_drive.default };
-const $charts_user_following: Provider = { provide: 'ep:charts/user/following', useClass: ep___charts_user_following.default };
-const $charts_user_notes: Provider = { provide: 'ep:charts/user/notes', useClass: ep___charts_user_notes.default };
-const $charts_user_pv: Provider = { provide: 'ep:charts/user/pv', useClass: ep___charts_user_pv.default };
-const $charts_user_reactions: Provider = { provide: 'ep:charts/user/reactions', useClass: ep___charts_user_reactions.default };
-const $charts_users: Provider = { provide: 'ep:charts/users', useClass: ep___charts_users.default };
-const $clips_addNote: Provider = { provide: 'ep:clips/add-note', useClass: ep___clips_addNote.default };
-const $clips_removeNote: Provider = { provide: 'ep:clips/remove-note', useClass: ep___clips_removeNote.default };
-const $clips_create: Provider = { provide: 'ep:clips/create', useClass: ep___clips_create.default };
-const $clips_delete: Provider = { provide: 'ep:clips/delete', useClass: ep___clips_delete.default };
-const $clips_list: Provider = { provide: 'ep:clips/list', useClass: ep___clips_list.default };
-const $clips_notes: Provider = { provide: 'ep:clips/notes', useClass: ep___clips_notes.default };
-const $clips_show: Provider = { provide: 'ep:clips/show', useClass: ep___clips_show.default };
-const $clips_update: Provider = { provide: 'ep:clips/update', useClass: ep___clips_update.default };
-const $clips_favorite: Provider = { provide: 'ep:clips/favorite', useClass: ep___clips_favorite.default };
-const $clips_unfavorite: Provider = { provide: 'ep:clips/unfavorite', useClass: ep___clips_unfavorite.default };
-const $clips_myFavorites: Provider = { provide: 'ep:clips/my-favorites', useClass: ep___clips_myFavorites.default };
-const $drive: Provider = { provide: 'ep:drive', useClass: ep___drive.default };
-const $drive_files: Provider = { provide: 'ep:drive/files', useClass: ep___drive_files.default };
-const $drive_files_attachedNotes: Provider = { provide: 'ep:drive/files/attached-notes', useClass: ep___drive_files_attachedNotes.default };
-const $drive_files_checkExistence: Provider = { provide: 'ep:drive/files/check-existence', useClass: ep___drive_files_checkExistence.default };
-const $drive_files_create: Provider = { provide: 'ep:drive/files/create', useClass: ep___drive_files_create.default };
-const $drive_files_delete: Provider = { provide: 'ep:drive/files/delete', useClass: ep___drive_files_delete.default };
-const $drive_files_findByHash: Provider = { provide: 'ep:drive/files/find-by-hash', useClass: ep___drive_files_findByHash.default };
-const $drive_files_find: Provider = { provide: 'ep:drive/files/find', useClass: ep___drive_files_find.default };
-const $drive_files_show: Provider = { provide: 'ep:drive/files/show', useClass: ep___drive_files_show.default };
-const $drive_files_update: Provider = { provide: 'ep:drive/files/update', useClass: ep___drive_files_update.default };
-const $drive_files_uploadFromUrl: Provider = { provide: 'ep:drive/files/upload-from-url', useClass: ep___drive_files_uploadFromUrl.default };
-const $drive_folders: Provider = { provide: 'ep:drive/folders', useClass: ep___drive_folders.default };
-const $drive_folders_create: Provider = { provide: 'ep:drive/folders/create', useClass: ep___drive_folders_create.default };
-const $drive_folders_delete: Provider = { provide: 'ep:drive/folders/delete', useClass: ep___drive_folders_delete.default };
-const $drive_folders_find: Provider = { provide: 'ep:drive/folders/find', useClass: ep___drive_folders_find.default };
-const $drive_folders_show: Provider = { provide: 'ep:drive/folders/show', useClass: ep___drive_folders_show.default };
-const $drive_folders_update: Provider = { provide: 'ep:drive/folders/update', useClass: ep___drive_folders_update.default };
-const $drive_stream: Provider = { provide: 'ep:drive/stream', useClass: ep___drive_stream.default };
-const $emailAddress_available: Provider = { provide: 'ep:email-address/available', useClass: ep___emailAddress_available.default };
-const $endpoint: Provider = { provide: 'ep:endpoint', useClass: ep___endpoint.default };
-const $endpoints: Provider = { provide: 'ep:endpoints', useClass: ep___endpoints.default };
-const $exportCustomEmojis: Provider = { provide: 'ep:export-custom-emojis', useClass: ep___exportCustomEmojis.default };
-const $federation_followers: Provider = { provide: 'ep:federation/followers', useClass: ep___federation_followers.default };
-const $federation_following: Provider = { provide: 'ep:federation/following', useClass: ep___federation_following.default };
-const $federation_instances: Provider = { provide: 'ep:federation/instances', useClass: ep___federation_instances.default };
-const $federation_showInstance: Provider = { provide: 'ep:federation/show-instance', useClass: ep___federation_showInstance.default };
-const $federation_updateRemoteUser: Provider = { provide: 'ep:federation/update-remote-user', useClass: ep___federation_updateRemoteUser.default };
-const $federation_users: Provider = { provide: 'ep:federation/users', useClass: ep___federation_users.default };
-const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default };
-const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default };
-const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default };
-const $following_update: Provider = { provide: 'ep:following/update', useClass: ep___following_update.default };
-const $following_update_all: Provider = { provide: 'ep:following/update-all', useClass: ep___following_update_all.default };
-const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default };
-const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
-const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
-const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default };
-const $following_requests_sent: Provider = { provide: 'ep:following/requests/sent', useClass: ep___following_requests_sent.default };
-const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default };
-const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default };
-const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default };
-const $gallery_posts: Provider = { provide: 'ep:gallery/posts', useClass: ep___gallery_posts.default };
-const $gallery_posts_create: Provider = { provide: 'ep:gallery/posts/create', useClass: ep___gallery_posts_create.default };
-const $gallery_posts_delete: Provider = { provide: 'ep:gallery/posts/delete', useClass: ep___gallery_posts_delete.default };
-const $gallery_posts_like: Provider = { provide: 'ep:gallery/posts/like', useClass: ep___gallery_posts_like.default };
-const $gallery_posts_show: Provider = { provide: 'ep:gallery/posts/show', useClass: ep___gallery_posts_show.default };
-const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default };
-const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default };
-const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default };
-const $getAvatarDecorations: Provider = { provide: 'ep:get-avatar-decorations', useClass: ep___getAvatarDecorations.default };
-const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default };
-const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default };
-const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default };
-const $hashtags_trend: Provider = { provide: 'ep:hashtags/trend', useClass: ep___hashtags_trend.default };
-const $hashtags_users: Provider = { provide: 'ep:hashtags/users', useClass: ep___hashtags_users.default };
-const $i: Provider = { provide: 'ep:i', useClass: ep___i.default };
-const $i_2fa_done: Provider = { provide: 'ep:i/2fa/done', useClass: ep___i_2fa_done.default };
-const $i_2fa_keyDone: Provider = { provide: 'ep:i/2fa/key-done', useClass: ep___i_2fa_keyDone.default };
-const $i_2fa_passwordLess: Provider = { provide: 'ep:i/2fa/password-less', useClass: ep___i_2fa_passwordLess.default };
-const $i_2fa_registerKey: Provider = { provide: 'ep:i/2fa/register-key', useClass: ep___i_2fa_registerKey.default };
-const $i_2fa_register: Provider = { provide: 'ep:i/2fa/register', useClass: ep___i_2fa_register.default };
-const $i_2fa_updateKey: Provider = { provide: 'ep:i/2fa/update-key', useClass: ep___i_2fa_updateKey.default };
-const $i_2fa_removeKey: Provider = { provide: 'ep:i/2fa/remove-key', useClass: ep___i_2fa_removeKey.default };
-const $i_2fa_unregister: Provider = { provide: 'ep:i/2fa/unregister', useClass: ep___i_2fa_unregister.default };
-const $i_apps: Provider = { provide: 'ep:i/apps', useClass: ep___i_apps.default };
-const $i_authorizedApps: Provider = { provide: 'ep:i/authorized-apps', useClass: ep___i_authorizedApps.default };
-const $i_claimAchievement: Provider = { provide: 'ep:i/claim-achievement', useClass: ep___i_claimAchievement.default };
-const $i_changePassword: Provider = { provide: 'ep:i/change-password', useClass: ep___i_changePassword.default };
-const $i_deleteAccount: Provider = { provide: 'ep:i/delete-account', useClass: ep___i_deleteAccount.default };
-const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: ep___i_exportBlocking.default };
-const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default };
-const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default };
-const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
-const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default };
-const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
-const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
-const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
-const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default };
-const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default };
-const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default };
-const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default };
-const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default };
-const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default };
-const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
-const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
-const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
-const $i_notificationsGrouped: Provider = { provide: 'ep:i/notifications-grouped', useClass: ep___i_notificationsGrouped.default };
-const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
-const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
-const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default };
-const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default };
-const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default };
-const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default };
-const $i_registry_getAll: Provider = { provide: 'ep:i/registry/get-all', useClass: ep___i_registry_getAll.default };
-const $i_registry_getDetail: Provider = { provide: 'ep:i/registry/get-detail', useClass: ep___i_registry_getDetail.default };
-const $i_registry_get: Provider = { provide: 'ep:i/registry/get', useClass: ep___i_registry_get.default };
-const $i_registry_keysWithType: Provider = { provide: 'ep:i/registry/keys-with-type', useClass: ep___i_registry_keysWithType.default };
-const $i_registry_keys: Provider = { provide: 'ep:i/registry/keys', useClass: ep___i_registry_keys.default };
-const $i_registry_remove: Provider = { provide: 'ep:i/registry/remove', useClass: ep___i_registry_remove.default };
-const $i_registry_scopesWithDomain: Provider = { provide: 'ep:i/registry/scopes-with-domain', useClass: ep___i_registry_scopesWithDomain.default };
-const $i_registry_set: Provider = { provide: 'ep:i/registry/set', useClass: ep___i_registry_set.default };
-const $i_revokeToken: Provider = { provide: 'ep:i/revoke-token', useClass: ep___i_revokeToken.default };
-const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: ep___i_signinHistory.default };
-const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default };
-const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
-const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
-const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
-const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
-const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
-const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
-const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default };
-const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
-const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default };
-const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default };
-const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default };
-const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default };
-const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
-const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
-const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
-const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
-const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
-const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
-const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
-const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
-const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
-const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
-const $renoteMute_list: Provider = { provide: 'ep:renote-mute/list', useClass: ep___renoteMute_list.default };
-const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default };
-const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default };
-const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default };
-const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default };
-const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
-const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
-const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
-const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
-const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
-const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
-const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
-const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
-const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
-const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
-const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
-const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
-const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default };
-const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default };
-const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default };
-const $notes_renotes: Provider = { provide: 'ep:notes/renotes', useClass: ep___notes_renotes.default };
-const $notes_replies: Provider = { provide: 'ep:notes/replies', useClass: ep___notes_replies.default };
-const $notes_searchByTag: Provider = { provide: 'ep:notes/search-by-tag', useClass: ep___notes_searchByTag.default };
-const $notes_search: Provider = { provide: 'ep:notes/search', useClass: ep___notes_search.default };
-const $notes_show: Provider = { provide: 'ep:notes/show', useClass: ep___notes_show.default };
-const $notes_state: Provider = { provide: 'ep:notes/state', useClass: ep___notes_state.default };
-const $notes_threadMuting_create: Provider = { provide: 'ep:notes/thread-muting/create', useClass: ep___notes_threadMuting_create.default };
-const $notes_threadMuting_delete: Provider = { provide: 'ep:notes/thread-muting/delete', useClass: ep___notes_threadMuting_delete.default };
-const $notes_timeline: Provider = { provide: 'ep:notes/timeline', useClass: ep___notes_timeline.default };
-const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep___notes_translate.default };
-const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
-const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
-const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
-const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
-const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
-const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
-const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
-const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
-const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
-const $pages_featured: Provider = { provide: 'ep:pages/featured', useClass: ep___pages_featured.default };
-const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_like.default };
-const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default };
-const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default };
-const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default };
-const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default };
-const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default };
-const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default };
-const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default };
-const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default };
-const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default };
-const $flash_update: Provider = { provide: 'ep:flash/update', useClass: ep___flash_update.default };
-const $flash_my: Provider = { provide: 'ep:flash/my', useClass: ep___flash_my.default };
-const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___flash_myLikes.default };
-const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default };
-const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default };
-const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default };
-const $roles_list: Provider = { provide: 'ep:roles/list', useClass: ep___roles_list.default };
-const $roles_show: Provider = { provide: 'ep:roles/show', useClass: ep___roles_show.default };
-const $roles_users: Provider = { provide: 'ep:roles/users', useClass: ep___roles_users.default };
-const $roles_notes: Provider = { provide: 'ep:roles/notes', useClass: ep___roles_notes.default };
-const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default };
-const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default };
-const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default };
-const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default };
-const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default };
-const $sw_show_registration: Provider = { provide: 'ep:sw/show-registration', useClass: ep___sw_show_registration.default };
-const $sw_update_registration: Provider = { provide: 'ep:sw/update-registration', useClass: ep___sw_update_registration.default };
-const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default };
-const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default };
-const $test: Provider = { provide: 'ep:test', useClass: ep___test.default };
-const $username_available: Provider = { provide: 'ep:username/available', useClass: ep___username_available.default };
-const $users: Provider = { provide: 'ep:users', useClass: ep___users.default };
-const $users_clips: Provider = { provide: 'ep:users/clips', useClass: ep___users_clips.default };
-const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep___users_followers.default };
-const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default };
-const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
-const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
-const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
-const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
-const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
-const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
-const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default };
-const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
-const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
-const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
-const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
-const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
-const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default };
-const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default };
-const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default };
-const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
-const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
-const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default };
-const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
-const $users_recommendation: Provider = { provide: 'ep:users/recommendation', useClass: ep___users_recommendation.default };
-const $users_relation: Provider = { provide: 'ep:users/relation', useClass: ep___users_relation.default };
-const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClass: ep___users_reportAbuse.default };
-const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default };
-const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default };
-const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
-const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
-const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
-const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
-const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
-const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
-const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
-const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
-const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default };
-const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default };
-const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.default };
-const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
-const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
-const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
-const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
+const endpoints = Object.entries(endpointsObject);
+const endpointProviders = endpoints.map(([path, endpoint]): Provider => ({ provide: `ep:${path}`, useClass: endpoint.default }));
 
 @Module({
 	imports: [
@@ -786,773 +21,10 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 	providers: [
 		GetterService,
 		ApiLoggerService,
-		$admin_meta,
-		$admin_abuseUserReports,
-		$admin_abuseReport_notificationRecipient_list,
-		$admin_abuseReport_notificationRecipient_show,
-		$admin_abuseReport_notificationRecipient_create,
-		$admin_abuseReport_notificationRecipient_update,
-		$admin_abuseReport_notificationRecipient_delete,
-		$admin_accounts_create,
-		$admin_accounts_delete,
-		$admin_accounts_findByEmail,
-		$admin_ad_create,
-		$admin_ad_delete,
-		$admin_ad_list,
-		$admin_ad_update,
-		$admin_announcements_create,
-		$admin_announcements_delete,
-		$admin_announcements_list,
-		$admin_announcements_update,
-		$admin_avatarDecorations_create,
-		$admin_avatarDecorations_delete,
-		$admin_avatarDecorations_list,
-		$admin_avatarDecorations_update,
-		$admin_deleteAllFilesOfAUser,
-		$admin_unsetUserAvatar,
-		$admin_unsetUserBanner,
-		$admin_drive_cleanRemoteFiles,
-		$admin_drive_cleanup,
-		$admin_drive_files,
-		$admin_drive_showFile,
-		$admin_emoji_addAliasesBulk,
-		$admin_emoji_add,
-		$admin_emoji_copy,
-		$admin_emoji_deleteBulk,
-		$admin_emoji_delete,
-		$admin_emoji_importZip,
-		$admin_emoji_listRemote,
-		$admin_emoji_list,
-		$admin_emoji_removeAliasesBulk,
-		$admin_emoji_setAliasesBulk,
-		$admin_emoji_setCategoryBulk,
-		$admin_emoji_setLicenseBulk,
-		$admin_emoji_update,
-		$admin_federation_deleteAllFiles,
-		$admin_federation_refreshRemoteInstanceMetadata,
-		$admin_federation_removeAllFollowing,
-		$admin_federation_updateInstance,
-		$admin_getIndexStats,
-		$admin_getTableStats,
-		$admin_getUserIps,
-		$admin_invite_create,
-		$admin_invite_list,
-		$admin_promo_create,
-		$admin_queue_clear,
-		$admin_queue_deliverDelayed,
-		$admin_queue_inboxDelayed,
-		$admin_queue_promote,
-		$admin_queue_stats,
-		$admin_relays_add,
-		$admin_relays_list,
-		$admin_relays_remove,
-		$admin_resetPassword,
-		$admin_resolveAbuseUserReport,
-		$admin_forwardAbuseUserReport,
-		$admin_updateAbuseUserReport,
-		$admin_sendEmail,
-		$admin_serverInfo,
-		$admin_showModerationLogs,
-		$admin_showUser,
-		$admin_showUsers,
-		$admin_suspendUser,
-		$admin_unsuspendUser,
-		$admin_updateMeta,
-		$admin_deleteAccount,
-		$admin_updateUserNote,
-		$admin_roles_create,
-		$admin_roles_delete,
-		$admin_roles_list,
-		$admin_roles_show,
-		$admin_roles_update,
-		$admin_roles_assign,
-		$admin_roles_unassign,
-		$admin_roles_updateDefaultPolicies,
-		$admin_roles_users,
-		$admin_systemWebhook_create,
-		$admin_systemWebhook_delete,
-		$admin_systemWebhook_list,
-		$admin_systemWebhook_show,
-		$admin_systemWebhook_update,
-		$admin_systemWebhook_test,
-		$announcements,
-		$announcements_show,
-		$antennas_create,
-		$antennas_delete,
-		$antennas_list,
-		$antennas_notes,
-		$antennas_show,
-		$antennas_update,
-		$ap_get,
-		$ap_show,
-		$app_create,
-		$app_show,
-		$auth_accept,
-		$auth_session_generate,
-		$auth_session_show,
-		$auth_session_userkey,
-		$blocking_create,
-		$blocking_delete,
-		$blocking_list,
-		$channels_create,
-		$channels_featured,
-		$channels_follow,
-		$channels_followed,
-		$channels_owned,
-		$channels_show,
-		$channels_timeline,
-		$channels_unfollow,
-		$channels_update,
-		$channels_favorite,
-		$channels_unfavorite,
-		$channels_myFavorites,
-		$channels_search,
-		$charts_activeUsers,
-		$charts_apRequest,
-		$charts_drive,
-		$charts_federation,
-		$charts_instance,
-		$charts_notes,
-		$charts_user_drive,
-		$charts_user_following,
-		$charts_user_notes,
-		$charts_user_pv,
-		$charts_user_reactions,
-		$charts_users,
-		$clips_addNote,
-		$clips_removeNote,
-		$clips_create,
-		$clips_delete,
-		$clips_list,
-		$clips_notes,
-		$clips_show,
-		$clips_update,
-		$clips_favorite,
-		$clips_unfavorite,
-		$clips_myFavorites,
-		$drive,
-		$drive_files,
-		$drive_files_attachedNotes,
-		$drive_files_checkExistence,
-		$drive_files_create,
-		$drive_files_delete,
-		$drive_files_findByHash,
-		$drive_files_find,
-		$drive_files_show,
-		$drive_files_update,
-		$drive_files_uploadFromUrl,
-		$drive_folders,
-		$drive_folders_create,
-		$drive_folders_delete,
-		$drive_folders_find,
-		$drive_folders_show,
-		$drive_folders_update,
-		$drive_stream,
-		$emailAddress_available,
-		$endpoint,
-		$endpoints,
-		$exportCustomEmojis,
-		$federation_followers,
-		$federation_following,
-		$federation_instances,
-		$federation_showInstance,
-		$federation_updateRemoteUser,
-		$federation_users,
-		$federation_stats,
-		$following_create,
-		$following_delete,
-		$following_update,
-		$following_update_all,
-		$following_invalidate,
-		$following_requests_accept,
-		$following_requests_cancel,
-		$following_requests_list,
-		$following_requests_sent,
-		$following_requests_reject,
-		$gallery_featured,
-		$gallery_popular,
-		$gallery_posts,
-		$gallery_posts_create,
-		$gallery_posts_delete,
-		$gallery_posts_like,
-		$gallery_posts_show,
-		$gallery_posts_unlike,
-		$gallery_posts_update,
-		$getOnlineUsersCount,
-		$getAvatarDecorations,
-		$hashtags_list,
-		$hashtags_search,
-		$hashtags_show,
-		$hashtags_trend,
-		$hashtags_users,
-		$i,
-		$i_2fa_done,
-		$i_2fa_keyDone,
-		$i_2fa_passwordLess,
-		$i_2fa_registerKey,
-		$i_2fa_register,
-		$i_2fa_updateKey,
-		$i_2fa_removeKey,
-		$i_2fa_unregister,
-		$i_apps,
-		$i_authorizedApps,
-		$i_claimAchievement,
-		$i_changePassword,
-		$i_deleteAccount,
-		$i_exportBlocking,
-		$i_exportFollowing,
-		$i_exportMute,
-		$i_exportNotes,
-		$i_exportClips,
-		$i_exportFavorites,
-		$i_exportUserLists,
-		$i_exportAntennas,
-		$i_favorites,
-		$i_gallery_likes,
-		$i_gallery_posts,
-		$i_importBlocking,
-		$i_importFollowing,
-		$i_importMuting,
-		$i_importUserLists,
-		$i_importAntennas,
-		$i_notifications,
-		$i_notificationsGrouped,
-		$i_pageLikes,
-		$i_pages,
-		$i_pin,
-		$i_readAllUnreadNotes,
-		$i_readAnnouncement,
-		$i_regenerateToken,
-		$i_registry_getAll,
-		$i_registry_getDetail,
-		$i_registry_get,
-		$i_registry_keysWithType,
-		$i_registry_keys,
-		$i_registry_remove,
-		$i_registry_scopesWithDomain,
-		$i_registry_set,
-		$i_revokeToken,
-		$i_signinHistory,
-		$i_unpin,
-		$i_updateEmail,
-		$i_update,
-		$i_move,
-		$i_webhooks_create,
-		$i_webhooks_list,
-		$i_webhooks_show,
-		$i_webhooks_update,
-		$i_webhooks_delete,
-		$i_webhooks_test,
-		$invite_create,
-		$invite_delete,
-		$invite_list,
-		$invite_limit,
-		$meta,
-		$emojis,
-		$emoji,
-		$miauth_genToken,
-		$mute_create,
-		$mute_delete,
-		$mute_list,
-		$renoteMute_create,
-		$renoteMute_delete,
-		$renoteMute_list,
-		$my_apps,
-		$notes,
-		$notes_children,
-		$notes_clips,
-		$notes_conversation,
-		$notes_create,
-		$notes_delete,
-		$notes_favorites_create,
-		$notes_favorites_delete,
-		$notes_featured,
-		$notes_globalTimeline,
-		$notes_hybridTimeline,
-		$notes_localTimeline,
-		$notes_mentions,
-		$notes_polls_recommendation,
-		$notes_polls_vote,
-		$notes_reactions,
-		$notes_reactions_create,
-		$notes_reactions_delete,
-		$notes_renotes,
-		$notes_replies,
-		$notes_searchByTag,
-		$notes_search,
-		$notes_show,
-		$notes_state,
-		$notes_threadMuting_create,
-		$notes_threadMuting_delete,
-		$notes_timeline,
-		$notes_translate,
-		$notes_unrenote,
-		$notes_userListTimeline,
-		$notifications_create,
-		$notifications_flush,
-		$notifications_markAllAsRead,
-		$notifications_testNotification,
-		$pagePush,
-		$pages_create,
-		$pages_delete,
-		$pages_featured,
-		$pages_like,
-		$pages_show,
-		$pages_unlike,
-		$pages_update,
-		$flash_create,
-		$flash_delete,
-		$flash_featured,
-		$flash_like,
-		$flash_show,
-		$flash_unlike,
-		$flash_update,
-		$flash_my,
-		$flash_myLikes,
-		$ping,
-		$pinnedUsers,
-		$promo_read,
-		$roles_list,
-		$roles_show,
-		$roles_users,
-		$roles_notes,
-		$requestResetPassword,
-		$resetDb,
-		$resetPassword,
-		$serverInfo,
-		$stats,
-		$sw_show_registration,
-		$sw_update_registration,
-		$sw_register,
-		$sw_unregister,
-		$test,
-		$username_available,
-		$users,
-		$users_clips,
-		$users_followers,
-		$users_following,
-		$users_gallery_posts,
-		$users_getFrequentlyRepliedUsers,
-		$users_featuredNotes,
-		$users_lists_create,
-		$users_lists_delete,
-		$users_lists_list,
-		$users_lists_pull,
-		$users_lists_push,
-		$users_lists_show,
-		$users_lists_update,
-		$users_lists_favorite,
-		$users_lists_unfavorite,
-		$users_lists_createFromPublic,
-		$users_lists_updateMembership,
-		$users_lists_getMemberships,
-		$users_notes,
-		$users_pages,
-		$users_flashs,
-		$users_reactions,
-		$users_recommendation,
-		$users_relation,
-		$users_reportAbuse,
-		$users_searchByUsernameAndHost,
-		$users_search,
-		$users_show,
-		$users_achievements,
-		$users_updateMemo,
-		$fetchRss,
-		$fetchExternalResources,
-		$retention,
-		$bubbleGame_register,
-		$bubbleGame_ranking,
-		$reversi_cancelMatch,
-		$reversi_games,
-		$reversi_match,
-		$reversi_invitations,
-		$reversi_showGame,
-		$reversi_surrender,
-		$reversi_verify,
+		...endpointProviders,
 	],
 	exports: [
-		$admin_meta,
-		$admin_abuseUserReports,
-		$admin_abuseReport_notificationRecipient_list,
-		$admin_abuseReport_notificationRecipient_show,
-		$admin_abuseReport_notificationRecipient_create,
-		$admin_abuseReport_notificationRecipient_update,
-		$admin_abuseReport_notificationRecipient_delete,
-		$admin_accounts_create,
-		$admin_accounts_delete,
-		$admin_accounts_findByEmail,
-		$admin_ad_create,
-		$admin_ad_delete,
-		$admin_ad_list,
-		$admin_ad_update,
-		$admin_announcements_create,
-		$admin_announcements_delete,
-		$admin_announcements_list,
-		$admin_announcements_update,
-		$admin_avatarDecorations_create,
-		$admin_avatarDecorations_delete,
-		$admin_avatarDecorations_list,
-		$admin_avatarDecorations_update,
-		$admin_deleteAllFilesOfAUser,
-		$admin_unsetUserAvatar,
-		$admin_unsetUserBanner,
-		$admin_drive_cleanRemoteFiles,
-		$admin_drive_cleanup,
-		$admin_drive_files,
-		$admin_drive_showFile,
-		$admin_emoji_addAliasesBulk,
-		$admin_emoji_add,
-		$admin_emoji_copy,
-		$admin_emoji_deleteBulk,
-		$admin_emoji_delete,
-		$admin_emoji_importZip,
-		$admin_emoji_listRemote,
-		$admin_emoji_list,
-		$admin_emoji_removeAliasesBulk,
-		$admin_emoji_setAliasesBulk,
-		$admin_emoji_setCategoryBulk,
-		$admin_emoji_setLicenseBulk,
-		$admin_emoji_update,
-		$admin_federation_deleteAllFiles,
-		$admin_federation_refreshRemoteInstanceMetadata,
-		$admin_federation_removeAllFollowing,
-		$admin_federation_updateInstance,
-		$admin_getIndexStats,
-		$admin_getTableStats,
-		$admin_getUserIps,
-		$admin_invite_create,
-		$admin_invite_list,
-		$admin_promo_create,
-		$admin_queue_clear,
-		$admin_queue_deliverDelayed,
-		$admin_queue_inboxDelayed,
-		$admin_queue_promote,
-		$admin_queue_stats,
-		$admin_relays_add,
-		$admin_relays_list,
-		$admin_relays_remove,
-		$admin_resetPassword,
-		$admin_resolveAbuseUserReport,
-		$admin_forwardAbuseUserReport,
-		$admin_updateAbuseUserReport,
-		$admin_sendEmail,
-		$admin_serverInfo,
-		$admin_showModerationLogs,
-		$admin_showUser,
-		$admin_showUsers,
-		$admin_suspendUser,
-		$admin_unsuspendUser,
-		$admin_updateMeta,
-		$admin_deleteAccount,
-		$admin_updateUserNote,
-		$admin_roles_create,
-		$admin_roles_delete,
-		$admin_roles_list,
-		$admin_roles_show,
-		$admin_roles_update,
-		$admin_roles_assign,
-		$admin_roles_unassign,
-		$admin_roles_updateDefaultPolicies,
-		$admin_roles_users,
-		$admin_systemWebhook_create,
-		$admin_systemWebhook_delete,
-		$admin_systemWebhook_list,
-		$admin_systemWebhook_show,
-		$admin_systemWebhook_update,
-		$admin_systemWebhook_test,
-		$announcements,
-		$announcements_show,
-		$antennas_create,
-		$antennas_delete,
-		$antennas_list,
-		$antennas_notes,
-		$antennas_show,
-		$antennas_update,
-		$ap_get,
-		$ap_show,
-		$app_create,
-		$app_show,
-		$auth_accept,
-		$auth_session_generate,
-		$auth_session_show,
-		$auth_session_userkey,
-		$blocking_create,
-		$blocking_delete,
-		$blocking_list,
-		$channels_create,
-		$channels_featured,
-		$channels_follow,
-		$channels_followed,
-		$channels_owned,
-		$channels_show,
-		$channels_timeline,
-		$channels_unfollow,
-		$channels_update,
-		$channels_favorite,
-		$channels_unfavorite,
-		$channels_myFavorites,
-		$channels_search,
-		$charts_activeUsers,
-		$charts_apRequest,
-		$charts_drive,
-		$charts_federation,
-		$charts_instance,
-		$charts_notes,
-		$charts_user_drive,
-		$charts_user_following,
-		$charts_user_notes,
-		$charts_user_pv,
-		$charts_user_reactions,
-		$charts_users,
-		$clips_addNote,
-		$clips_removeNote,
-		$clips_create,
-		$clips_delete,
-		$clips_list,
-		$clips_notes,
-		$clips_show,
-		$clips_update,
-		$clips_favorite,
-		$clips_unfavorite,
-		$clips_myFavorites,
-		$drive,
-		$drive_files,
-		$drive_files_attachedNotes,
-		$drive_files_checkExistence,
-		$drive_files_create,
-		$drive_files_delete,
-		$drive_files_findByHash,
-		$drive_files_find,
-		$drive_files_show,
-		$drive_files_update,
-		$drive_files_uploadFromUrl,
-		$drive_folders,
-		$drive_folders_create,
-		$drive_folders_delete,
-		$drive_folders_find,
-		$drive_folders_show,
-		$drive_folders_update,
-		$drive_stream,
-		$emailAddress_available,
-		$endpoint,
-		$endpoints,
-		$exportCustomEmojis,
-		$federation_followers,
-		$federation_following,
-		$federation_instances,
-		$federation_showInstance,
-		$federation_updateRemoteUser,
-		$federation_users,
-		$federation_stats,
-		$following_create,
-		$following_delete,
-		$following_update,
-		$following_update_all,
-		$following_invalidate,
-		$following_requests_accept,
-		$following_requests_cancel,
-		$following_requests_list,
-		$following_requests_reject,
-		$gallery_featured,
-		$gallery_popular,
-		$gallery_posts,
-		$gallery_posts_create,
-		$gallery_posts_delete,
-		$gallery_posts_like,
-		$gallery_posts_show,
-		$gallery_posts_unlike,
-		$gallery_posts_update,
-		$getOnlineUsersCount,
-		$getAvatarDecorations,
-		$hashtags_list,
-		$hashtags_search,
-		$hashtags_show,
-		$hashtags_trend,
-		$hashtags_users,
-		$i,
-		$i_2fa_done,
-		$i_2fa_keyDone,
-		$i_2fa_passwordLess,
-		$i_2fa_registerKey,
-		$i_2fa_register,
-		$i_2fa_updateKey,
-		$i_2fa_removeKey,
-		$i_2fa_unregister,
-		$i_apps,
-		$i_authorizedApps,
-		$i_claimAchievement,
-		$i_changePassword,
-		$i_deleteAccount,
-		$i_exportBlocking,
-		$i_exportFollowing,
-		$i_exportMute,
-		$i_exportNotes,
-		$i_exportClips,
-		$i_exportFavorites,
-		$i_exportUserLists,
-		$i_exportAntennas,
-		$i_favorites,
-		$i_gallery_likes,
-		$i_gallery_posts,
-		$i_importBlocking,
-		$i_importFollowing,
-		$i_importMuting,
-		$i_importUserLists,
-		$i_importAntennas,
-		$i_notifications,
-		$i_notificationsGrouped,
-		$i_pageLikes,
-		$i_pages,
-		$i_pin,
-		$i_readAllUnreadNotes,
-		$i_readAnnouncement,
-		$i_regenerateToken,
-		$i_registry_getAll,
-		$i_registry_getDetail,
-		$i_registry_get,
-		$i_registry_keysWithType,
-		$i_registry_keys,
-		$i_registry_remove,
-		$i_registry_scopesWithDomain,
-		$i_registry_set,
-		$i_revokeToken,
-		$i_signinHistory,
-		$i_unpin,
-		$i_updateEmail,
-		$i_update,
-		$i_move,
-		$i_webhooks_create,
-		$i_webhooks_list,
-		$i_webhooks_show,
-		$i_webhooks_update,
-		$i_webhooks_delete,
-		$i_webhooks_test,
-		$invite_create,
-		$invite_delete,
-		$invite_list,
-		$invite_limit,
-		$meta,
-		$emojis,
-		$emoji,
-		$miauth_genToken,
-		$mute_create,
-		$mute_delete,
-		$mute_list,
-		$renoteMute_create,
-		$renoteMute_delete,
-		$renoteMute_list,
-		$my_apps,
-		$notes,
-		$notes_children,
-		$notes_clips,
-		$notes_conversation,
-		$notes_create,
-		$notes_delete,
-		$notes_favorites_create,
-		$notes_favorites_delete,
-		$notes_featured,
-		$notes_globalTimeline,
-		$notes_hybridTimeline,
-		$notes_localTimeline,
-		$notes_mentions,
-		$notes_polls_recommendation,
-		$notes_polls_vote,
-		$notes_reactions,
-		$notes_reactions_create,
-		$notes_reactions_delete,
-		$notes_renotes,
-		$notes_replies,
-		$notes_searchByTag,
-		$notes_search,
-		$notes_show,
-		$notes_state,
-		$notes_threadMuting_create,
-		$notes_threadMuting_delete,
-		$notes_timeline,
-		$notes_translate,
-		$notes_unrenote,
-		$notes_userListTimeline,
-		$notifications_create,
-		$notifications_flush,
-		$notifications_markAllAsRead,
-		$notifications_testNotification,
-		$pagePush,
-		$pages_create,
-		$pages_delete,
-		$pages_featured,
-		$pages_like,
-		$pages_show,
-		$pages_unlike,
-		$pages_update,
-		$flash_create,
-		$flash_delete,
-		$flash_featured,
-		$flash_like,
-		$flash_show,
-		$flash_unlike,
-		$flash_update,
-		$flash_my,
-		$flash_myLikes,
-		$ping,
-		$pinnedUsers,
-		$promo_read,
-		$roles_list,
-		$roles_show,
-		$roles_users,
-		$roles_notes,
-		$requestResetPassword,
-		$resetDb,
-		$resetPassword,
-		$serverInfo,
-		$stats,
-		$sw_register,
-		$sw_unregister,
-		$test,
-		$username_available,
-		$users,
-		$users_clips,
-		$users_followers,
-		$users_following,
-		$users_gallery_posts,
-		$users_getFrequentlyRepliedUsers,
-		$users_featuredNotes,
-		$users_lists_create,
-		$users_lists_delete,
-		$users_lists_list,
-		$users_lists_pull,
-		$users_lists_push,
-		$users_lists_show,
-		$users_lists_update,
-		$users_lists_favorite,
-		$users_lists_unfavorite,
-		$users_lists_createFromPublic,
-		$users_lists_updateMembership,
-		$users_lists_getMemberships,
-		$users_notes,
-		$users_pages,
-		$users_flashs,
-		$users_reactions,
-		$users_recommendation,
-		$users_relation,
-		$users_reportAbuse,
-		$users_searchByUsernameAndHost,
-		$users_search,
-		$users_show,
-		$users_achievements,
-		$users_updateMemo,
-		$fetchRss,
-		$fetchExternalResources,
-		$retention,
-		$bubbleGame_register,
-		$bubbleGame_ranking,
-		$reversi_cancelMatch,
-		$reversi_games,
-		$reversi_match,
-		$reversi_invitations,
-		$reversi_showGame,
-		$reversi_surrender,
-		$reversi_verify,
+		...endpointProviders,
 	],
 })
 export class EndpointsModule {}
diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts
new file mode 100644
index 000000000000..28f7cfea04a6
--- /dev/null
+++ b/packages/backend/src/server/api/endpoint-list.ts
@@ -0,0 +1,399 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/*
+ * This file contains list of all endpoints exported as pathname of API endpoint
+ *
+ * When you add new endpoint, you should add it to this file.
+ * This file is used to generate API documentation and EndpointsModule.
+ */
+
+export * as 'admin/abuse-report/notification-recipient/create' from './endpoints/admin/abuse-report/notification-recipient/create.js';
+export * as 'admin/abuse-report/notification-recipient/delete' from './endpoints/admin/abuse-report/notification-recipient/delete.js';
+export * as 'admin/abuse-report/notification-recipient/list' from './endpoints/admin/abuse-report/notification-recipient/list.js';
+export * as 'admin/abuse-report/notification-recipient/show' from './endpoints/admin/abuse-report/notification-recipient/show.js';
+export * as 'admin/abuse-report/notification-recipient/update' from './endpoints/admin/abuse-report/notification-recipient/update.js';
+export * as 'admin/abuse-user-reports' from './endpoints/admin/abuse-user-reports.js';
+export * as 'admin/accounts/create' from './endpoints/admin/accounts/create.js';
+export * as 'admin/accounts/delete' from './endpoints/admin/accounts/delete.js';
+export * as 'admin/accounts/find-by-email' from './endpoints/admin/accounts/find-by-email.js';
+export * as 'admin/ad/create' from './endpoints/admin/ad/create.js';
+export * as 'admin/ad/delete' from './endpoints/admin/ad/delete.js';
+export * as 'admin/ad/list' from './endpoints/admin/ad/list.js';
+export * as 'admin/ad/update' from './endpoints/admin/ad/update.js';
+export * as 'admin/announcements/create' from './endpoints/admin/announcements/create.js';
+export * as 'admin/announcements/delete' from './endpoints/admin/announcements/delete.js';
+export * as 'admin/announcements/list' from './endpoints/admin/announcements/list.js';
+export * as 'admin/announcements/update' from './endpoints/admin/announcements/update.js';
+export * as 'admin/avatar-decorations/create' from './endpoints/admin/avatar-decorations/create.js';
+export * as 'admin/avatar-decorations/delete' from './endpoints/admin/avatar-decorations/delete.js';
+export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decorations/list.js';
+export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js';
+export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js';
+export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js';
+export * as 'admin/delete-account' from './endpoints/admin/delete-account.js';
+export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js';
+export * as 'admin/drive/clean-remote-files' from './endpoints/admin/drive/clean-remote-files.js';
+export * as 'admin/drive/cleanup' from './endpoints/admin/drive/cleanup.js';
+export * as 'admin/drive/files' from './endpoints/admin/drive/files.js';
+export * as 'admin/drive/show-file' from './endpoints/admin/drive/show-file.js';
+export * as 'admin/emoji/add' from './endpoints/admin/emoji/add.js';
+export * as 'admin/emoji/add-aliases-bulk' from './endpoints/admin/emoji/add-aliases-bulk.js';
+export * as 'admin/emoji/copy' from './endpoints/admin/emoji/copy.js';
+export * as 'admin/emoji/delete' from './endpoints/admin/emoji/delete.js';
+export * as 'admin/emoji/delete-bulk' from './endpoints/admin/emoji/delete-bulk.js';
+export * as 'admin/emoji/import-zip' from './endpoints/admin/emoji/import-zip.js';
+export * as 'admin/emoji/list' from './endpoints/admin/emoji/list.js';
+export * as 'admin/emoji/list-remote' from './endpoints/admin/emoji/list-remote.js';
+export * as 'admin/emoji/remove-aliases-bulk' from './endpoints/admin/emoji/remove-aliases-bulk.js';
+export * as 'admin/emoji/set-aliases-bulk' from './endpoints/admin/emoji/set-aliases-bulk.js';
+export * as 'admin/emoji/set-category-bulk' from './endpoints/admin/emoji/set-category-bulk.js';
+export * as 'admin/emoji/set-license-bulk' from './endpoints/admin/emoji/set-license-bulk.js';
+export * as 'admin/emoji/update' from './endpoints/admin/emoji/update.js';
+export * as 'admin/federation/delete-all-files' from './endpoints/admin/federation/delete-all-files.js';
+export * as 'admin/federation/refresh-remote-instance-metadata' from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
+export * as 'admin/federation/remove-all-following' from './endpoints/admin/federation/remove-all-following.js';
+export * as 'admin/federation/update-instance' from './endpoints/admin/federation/update-instance.js';
+export * as 'admin/forward-abuse-user-report' from './endpoints/admin/forward-abuse-user-report.js';
+export * as 'admin/get-index-stats' from './endpoints/admin/get-index-stats.js';
+export * as 'admin/get-table-stats' from './endpoints/admin/get-table-stats.js';
+export * as 'admin/get-user-ips' from './endpoints/admin/get-user-ips.js';
+export * as 'admin/invite/create' from './endpoints/admin/invite/create.js';
+export * as 'admin/invite/list' from './endpoints/admin/invite/list.js';
+export * as 'admin/meta' from './endpoints/admin/meta.js';
+export * as 'admin/promo/create' from './endpoints/admin/promo/create.js';
+export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js';
+export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js';
+export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js';
+export * as 'admin/queue/promote' from './endpoints/admin/queue/promote.js';
+export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
+export * as 'admin/relays/add' from './endpoints/admin/relays/add.js';
+export * as 'admin/relays/list' from './endpoints/admin/relays/list.js';
+export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js';
+export * as 'admin/reset-password' from './endpoints/admin/reset-password.js';
+export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js';
+export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js';
+export * as 'admin/roles/create' from './endpoints/admin/roles/create.js';
+export * as 'admin/roles/delete' from './endpoints/admin/roles/delete.js';
+export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
+export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
+export * as 'admin/roles/unassign' from './endpoints/admin/roles/unassign.js';
+export * as 'admin/roles/update' from './endpoints/admin/roles/update.js';
+export * as 'admin/roles/update-default-policies' from './endpoints/admin/roles/update-default-policies.js';
+export * as 'admin/roles/users' from './endpoints/admin/roles/users.js';
+export * as 'admin/send-email' from './endpoints/admin/send-email.js';
+export * as 'admin/server-info' from './endpoints/admin/server-info.js';
+export * as 'admin/show-moderation-logs' from './endpoints/admin/show-moderation-logs.js';
+export * as 'admin/show-user' from './endpoints/admin/show-user.js';
+export * as 'admin/show-users' from './endpoints/admin/show-users.js';
+export * as 'admin/suspend-user' from './endpoints/admin/suspend-user.js';
+export * as 'admin/system-webhook/create' from './endpoints/admin/system-webhook/create.js';
+export * as 'admin/system-webhook/delete' from './endpoints/admin/system-webhook/delete.js';
+export * as 'admin/system-webhook/list' from './endpoints/admin/system-webhook/list.js';
+export * as 'admin/system-webhook/show' from './endpoints/admin/system-webhook/show.js';
+export * as 'admin/system-webhook/test' from './endpoints/admin/system-webhook/test.js';
+export * as 'admin/system-webhook/update' from './endpoints/admin/system-webhook/update.js';
+export * as 'admin/unset-user-avatar' from './endpoints/admin/unset-user-avatar.js';
+export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.js';
+export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
+export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
+export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
+export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
+export * as 'announcements' from './endpoints/announcements.js';
+export * as 'announcements/show' from './endpoints/announcements/show.js';
+export * as 'antennas/create' from './endpoints/antennas/create.js';
+export * as 'antennas/delete' from './endpoints/antennas/delete.js';
+export * as 'antennas/list' from './endpoints/antennas/list.js';
+export * as 'antennas/notes' from './endpoints/antennas/notes.js';
+export * as 'antennas/show' from './endpoints/antennas/show.js';
+export * as 'antennas/update' from './endpoints/antennas/update.js';
+export * as 'ap/get' from './endpoints/ap/get.js';
+export * as 'ap/show' from './endpoints/ap/show.js';
+export * as 'app/create' from './endpoints/app/create.js';
+export * as 'app/show' from './endpoints/app/show.js';
+export * as 'auth/accept' from './endpoints/auth/accept.js';
+export * as 'auth/session/generate' from './endpoints/auth/session/generate.js';
+export * as 'auth/session/show' from './endpoints/auth/session/show.js';
+export * as 'auth/session/userkey' from './endpoints/auth/session/userkey.js';
+export * as 'blocking/create' from './endpoints/blocking/create.js';
+export * as 'blocking/delete' from './endpoints/blocking/delete.js';
+export * as 'blocking/list' from './endpoints/blocking/list.js';
+export * as 'bubble-game/ranking' from './endpoints/bubble-game/ranking.js';
+export * as 'bubble-game/register' from './endpoints/bubble-game/register.js';
+export * as 'channels/create' from './endpoints/channels/create.js';
+export * as 'channels/favorite' from './endpoints/channels/favorite.js';
+export * as 'channels/featured' from './endpoints/channels/featured.js';
+export * as 'channels/follow' from './endpoints/channels/follow.js';
+export * as 'channels/followed' from './endpoints/channels/followed.js';
+export * as 'channels/my-favorites' from './endpoints/channels/my-favorites.js';
+export * as 'channels/owned' from './endpoints/channels/owned.js';
+export * as 'channels/search' from './endpoints/channels/search.js';
+export * as 'channels/show' from './endpoints/channels/show.js';
+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 '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';
+export * as 'charts/federation' from './endpoints/charts/federation.js';
+export * as 'charts/instance' from './endpoints/charts/instance.js';
+export * as 'charts/notes' from './endpoints/charts/notes.js';
+export * as 'charts/user/drive' from './endpoints/charts/user/drive.js';
+export * as 'charts/user/following' from './endpoints/charts/user/following.js';
+export * as 'charts/user/notes' from './endpoints/charts/user/notes.js';
+export * as 'charts/user/pv' from './endpoints/charts/user/pv.js';
+export * as 'charts/user/reactions' from './endpoints/charts/user/reactions.js';
+export * as 'charts/users' from './endpoints/charts/users.js';
+export * as 'clips/add-note' from './endpoints/clips/add-note.js';
+export * as 'clips/create' from './endpoints/clips/create.js';
+export * as 'clips/delete' from './endpoints/clips/delete.js';
+export * as 'clips/favorite' from './endpoints/clips/favorite.js';
+export * as 'clips/list' from './endpoints/clips/list.js';
+export * as 'clips/my-favorites' from './endpoints/clips/my-favorites.js';
+export * as 'clips/notes' from './endpoints/clips/notes.js';
+export * as 'clips/remove-note' from './endpoints/clips/remove-note.js';
+export * as 'clips/show' from './endpoints/clips/show.js';
+export * as 'clips/unfavorite' from './endpoints/clips/unfavorite.js';
+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/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';
+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/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';
+export * as 'drive/folders/delete' from './endpoints/drive/folders/delete.js';
+export * as 'drive/folders/find' from './endpoints/drive/folders/find.js';
+export * as 'drive/folders/show' from './endpoints/drive/folders/show.js';
+export * as 'drive/folders/update' from './endpoints/drive/folders/update.js';
+export * as 'drive/stream' from './endpoints/drive/stream.js';
+export * as 'email-address/available' from './endpoints/email-address/available.js';
+export * as 'emoji' from './endpoints/emoji.js';
+export * as 'emojis' from './endpoints/emojis.js';
+export * as 'endpoint' from './endpoints/endpoint.js';
+export * as 'endpoints' from './endpoints/endpoints.js';
+export * as 'export-custom-emojis' from './endpoints/export-custom-emojis.js';
+export * as 'federation/followers' from './endpoints/federation/followers.js';
+export * as 'federation/following' from './endpoints/federation/following.js';
+export * as 'federation/instances' from './endpoints/federation/instances.js';
+export * as 'federation/show-instance' from './endpoints/federation/show-instance.js';
+export * as 'federation/stats' from './endpoints/federation/stats.js';
+export * as 'federation/update-remote-user' from './endpoints/federation/update-remote-user.js';
+export * as 'federation/users' from './endpoints/federation/users.js';
+export * as 'fetch-external-resources' from './endpoints/fetch-external-resources.js';
+export * as 'fetch-rss' from './endpoints/fetch-rss.js';
+export * as 'flash/create' from './endpoints/flash/create.js';
+export * as 'flash/delete' from './endpoints/flash/delete.js';
+export * as 'flash/featured' from './endpoints/flash/featured.js';
+export * as 'flash/like' from './endpoints/flash/like.js';
+export * as 'flash/my' from './endpoints/flash/my.js';
+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 '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';
+export * as 'following/requests/accept' from './endpoints/following/requests/accept.js';
+export * as 'following/requests/cancel' from './endpoints/following/requests/cancel.js';
+export * as 'following/requests/list' from './endpoints/following/requests/list.js';
+export * as 'following/requests/reject' from './endpoints/following/requests/reject.js';
+export * as 'following/requests/sent' from './endpoints/following/requests/sent.js';
+export * as 'following/update' from './endpoints/following/update.js';
+export * as 'following/update-all' from './endpoints/following/update-all.js';
+export * as 'gallery/featured' from './endpoints/gallery/featured.js';
+export * as 'gallery/popular' from './endpoints/gallery/popular.js';
+export * as 'gallery/posts' from './endpoints/gallery/posts.js';
+export * as 'gallery/posts/create' from './endpoints/gallery/posts/create.js';
+export * as 'gallery/posts/delete' from './endpoints/gallery/posts/delete.js';
+export * as 'gallery/posts/like' from './endpoints/gallery/posts/like.js';
+export * as 'gallery/posts/show' from './endpoints/gallery/posts/show.js';
+export * as 'gallery/posts/unlike' from './endpoints/gallery/posts/unlike.js';
+export * as 'gallery/posts/update' from './endpoints/gallery/posts/update.js';
+export * as 'get-avatar-decorations' from './endpoints/get-avatar-decorations.js';
+export * as 'get-online-users-count' from './endpoints/get-online-users-count.js';
+export * as 'hashtags/list' from './endpoints/hashtags/list.js';
+export * as 'hashtags/search' from './endpoints/hashtags/search.js';
+export * as 'hashtags/show' from './endpoints/hashtags/show.js';
+export * as 'hashtags/trend' from './endpoints/hashtags/trend.js';
+export * as 'hashtags/users' from './endpoints/hashtags/users.js';
+export * as 'i' from './endpoints/i.js';
+export * as 'i/2fa/done' from './endpoints/i/2fa/done.js';
+export * as 'i/2fa/key-done' from './endpoints/i/2fa/key-done.js';
+export * as 'i/2fa/password-less' from './endpoints/i/2fa/password-less.js';
+export * as 'i/2fa/register' from './endpoints/i/2fa/register.js';
+export * as 'i/2fa/register-key' from './endpoints/i/2fa/register-key.js';
+export * as 'i/2fa/remove-key' from './endpoints/i/2fa/remove-key.js';
+export * as 'i/2fa/unregister' from './endpoints/i/2fa/unregister.js';
+export * as 'i/2fa/update-key' from './endpoints/i/2fa/update-key.js';
+export * as 'i/apps' from './endpoints/i/apps.js';
+export * as 'i/authorized-apps' from './endpoints/i/authorized-apps.js';
+export * as 'i/change-password' from './endpoints/i/change-password.js';
+export * as 'i/claim-achievement' from './endpoints/i/claim-achievement.js';
+export * as 'i/delete-account' from './endpoints/i/delete-account.js';
+export * as 'i/export-antennas' from './endpoints/i/export-antennas.js';
+export * as 'i/export-blocking' from './endpoints/i/export-blocking.js';
+export * as 'i/export-clips' from './endpoints/i/export-clips.js';
+export * as 'i/export-favorites' from './endpoints/i/export-favorites.js';
+export * as 'i/export-following' from './endpoints/i/export-following.js';
+export * as 'i/export-mute' from './endpoints/i/export-mute.js';
+export * as 'i/export-notes' from './endpoints/i/export-notes.js';
+export * as 'i/export-user-lists' from './endpoints/i/export-user-lists.js';
+export * as 'i/favorites' from './endpoints/i/favorites.js';
+export * as 'i/gallery/likes' from './endpoints/i/gallery/likes.js';
+export * as 'i/gallery/posts' from './endpoints/i/gallery/posts.js';
+export * as 'i/import-antennas' from './endpoints/i/import-antennas.js';
+export * as 'i/import-blocking' from './endpoints/i/import-blocking.js';
+export * as 'i/import-following' from './endpoints/i/import-following.js';
+export * as 'i/import-muting' from './endpoints/i/import-muting.js';
+export * as 'i/import-user-lists' from './endpoints/i/import-user-lists.js';
+export * as 'i/move' from './endpoints/i/move.js';
+export * as 'i/notifications' from './endpoints/i/notifications.js';
+export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.js';
+export * as 'i/page-likes' from './endpoints/i/page-likes.js';
+export * as 'i/pages' from './endpoints/i/pages.js';
+export * as 'i/pin' from './endpoints/i/pin.js';
+export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js';
+export * as 'i/read-announcement' from './endpoints/i/read-announcement.js';
+export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js';
+export * as 'i/registry/get' from './endpoints/i/registry/get.js';
+export * as 'i/registry/get-all' from './endpoints/i/registry/get-all.js';
+export * as 'i/registry/get-detail' from './endpoints/i/registry/get-detail.js';
+export * as 'i/registry/keys' from './endpoints/i/registry/keys.js';
+export * as 'i/registry/keys-with-type' from './endpoints/i/registry/keys-with-type.js';
+export * as 'i/registry/remove' from './endpoints/i/registry/remove.js';
+export * as 'i/registry/scopes-with-domain' from './endpoints/i/registry/scopes-with-domain.js';
+export * as 'i/registry/set' from './endpoints/i/registry/set.js';
+export * as 'i/revoke-token' from './endpoints/i/revoke-token.js';
+export * as 'i/signin-history' from './endpoints/i/signin-history.js';
+export * as 'i/unpin' from './endpoints/i/unpin.js';
+export * as 'i/update' from './endpoints/i/update.js';
+export * as 'i/update-email' from './endpoints/i/update-email.js';
+export * as 'i/webhooks/create' from './endpoints/i/webhooks/create.js';
+export * as 'i/webhooks/delete' from './endpoints/i/webhooks/delete.js';
+export * as 'i/webhooks/list' from './endpoints/i/webhooks/list.js';
+export * as 'i/webhooks/show' from './endpoints/i/webhooks/show.js';
+export * as 'i/webhooks/test' from './endpoints/i/webhooks/test.js';
+export * as 'i/webhooks/update' from './endpoints/i/webhooks/update.js';
+export * as 'invite/create' from './endpoints/invite/create.js';
+export * as 'invite/delete' from './endpoints/invite/delete.js';
+export * as 'invite/limit' from './endpoints/invite/limit.js';
+export * as 'invite/list' from './endpoints/invite/list.js';
+export * as 'meta' from './endpoints/meta.js';
+export * as 'miauth/gen-token' from './endpoints/miauth/gen-token.js';
+export * as 'mute/create' from './endpoints/mute/create.js';
+export * as 'mute/delete' from './endpoints/mute/delete.js';
+export * as 'mute/list' from './endpoints/mute/list.js';
+export * as 'my/apps' from './endpoints/my/apps.js';
+export * as 'notes' from './endpoints/notes.js';
+export * as 'notes/children' from './endpoints/notes/children.js';
+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/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';
+export * as 'notes/global-timeline' from './endpoints/notes/global-timeline.js';
+export * as 'notes/hybrid-timeline' from './endpoints/notes/hybrid-timeline.js';
+export * as 'notes/local-timeline' from './endpoints/notes/local-timeline.js';
+export * as 'notes/mentions' from './endpoints/notes/mentions.js';
+export * as 'notes/polls/recommendation' from './endpoints/notes/polls/recommendation.js';
+export * as 'notes/polls/vote' from './endpoints/notes/polls/vote.js';
+export * as 'notes/reactions' from './endpoints/notes/reactions.js';
+export * as 'notes/reactions/create' from './endpoints/notes/reactions/create.js';
+export * as 'notes/reactions/delete' from './endpoints/notes/reactions/delete.js';
+export * as 'notes/renotes' from './endpoints/notes/renotes.js';
+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/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';
+export * as 'notes/timeline' from './endpoints/notes/timeline.js';
+export * as 'notes/translate' from './endpoints/notes/translate.js';
+export * as 'notes/unrenote' from './endpoints/notes/unrenote.js';
+export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js';
+export * as 'notifications/create' from './endpoints/notifications/create.js';
+export * as 'notifications/flush' from './endpoints/notifications/flush.js';
+export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js';
+export * as 'notifications/test-notification' from './endpoints/notifications/test-notification.js';
+export * as 'page-push' from './endpoints/page-push.js';
+export * as 'pages/create' from './endpoints/pages/create.js';
+export * as 'pages/delete' from './endpoints/pages/delete.js';
+export * as 'pages/featured' from './endpoints/pages/featured.js';
+export * as 'pages/like' from './endpoints/pages/like.js';
+export * as 'pages/show' from './endpoints/pages/show.js';
+export * as 'pages/unlike' from './endpoints/pages/unlike.js';
+export * as 'pages/update' from './endpoints/pages/update.js';
+export * as 'ping' from './endpoints/ping.js';
+export * as 'pinned-users' from './endpoints/pinned-users.js';
+export * as 'promo/read' from './endpoints/promo/read.js';
+export * as 'renote-mute/create' from './endpoints/renote-mute/create.js';
+export * as 'renote-mute/delete' from './endpoints/renote-mute/delete.js';
+export * as 'renote-mute/list' from './endpoints/renote-mute/list.js';
+export * as 'request-reset-password' from './endpoints/request-reset-password.js';
+export * as 'reset-db' from './endpoints/reset-db.js';
+export * as 'reset-password' from './endpoints/reset-password.js';
+export * as 'retention' from './endpoints/retention.js';
+export * as 'reversi/cancel-match' from './endpoints/reversi/cancel-match.js';
+export * as 'reversi/games' from './endpoints/reversi/games.js';
+export * as 'reversi/invitations' from './endpoints/reversi/invitations.js';
+export * as 'reversi/match' from './endpoints/reversi/match.js';
+export * as 'reversi/show-game' from './endpoints/reversi/show-game.js';
+export * as 'reversi/surrender' from './endpoints/reversi/surrender.js';
+export * as 'reversi/verify' from './endpoints/reversi/verify.js';
+export * as 'roles/list' from './endpoints/roles/list.js';
+export * as 'roles/notes' from './endpoints/roles/notes.js';
+export * as 'roles/show' from './endpoints/roles/show.js';
+export * as 'roles/users' from './endpoints/roles/users.js';
+export * as 'server-info' from './endpoints/server-info.js';
+export * as 'stats' from './endpoints/stats.js';
+export * as 'sw/register' from './endpoints/sw/register.js';
+export * as 'sw/show-registration' from './endpoints/sw/show-registration.js';
+export * as 'sw/unregister' from './endpoints/sw/unregister.js';
+export * as 'sw/update-registration' from './endpoints/sw/update-registration.js';
+export * as 'test' from './endpoints/test.js';
+export * as 'username/available' from './endpoints/username/available.js';
+export * as 'users' from './endpoints/users.js';
+export * as 'users/achievements' from './endpoints/users/achievements.js';
+export * as 'users/clips' from './endpoints/users/clips.js';
+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/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';
+export * as 'users/lists/create-from-public' from './endpoints/users/lists/create-from-public.js';
+export * as 'users/lists/delete' from './endpoints/users/lists/delete.js';
+export * as 'users/lists/favorite' from './endpoints/users/lists/favorite.js';
+export * as 'users/lists/get-memberships' from './endpoints/users/lists/get-memberships.js';
+export * as 'users/lists/list' from './endpoints/users/lists/list.js';
+export * as 'users/lists/pull' from './endpoints/users/lists/pull.js';
+export * as 'users/lists/push' from './endpoints/users/lists/push.js';
+export * as 'users/lists/show' from './endpoints/users/lists/show.js';
+export * as 'users/lists/unfavorite' from './endpoints/users/lists/unfavorite.js';
+export * as 'users/lists/update' from './endpoints/users/lists/update.js';
+export * as 'users/lists/update-membership' from './endpoints/users/lists/update-membership.js';
+export * as 'users/notes' from './endpoints/users/notes.js';
+export * as 'users/pages' from './endpoints/users/pages.js';
+export * as 'users/reactions' from './endpoints/users/reactions.js';
+export * as 'users/recommendation' from './endpoints/users/recommendation.js';
+export * as 'users/relation' from './endpoints/users/relation.js';
+export * as 'users/report-abuse' from './endpoints/users/report-abuse.js';
+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 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 15809b2678ad..a9a2ebc04128 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -6,783 +6,7 @@
 import { permissions } from 'misskey-js';
 import type { KeyOf, Schema } from '@/misc/json-schema.js';
 
-import * as ep___admin_abuseReport_notificationRecipient_list
-	from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
-import * as ep___admin_abuseReport_notificationRecipient_show
-	from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
-import * as ep___admin_abuseReport_notificationRecipient_create
-	from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
-import * as ep___admin_abuseReport_notificationRecipient_update
-	from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
-import * as ep___admin_abuseReport_notificationRecipient_delete
-	from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
-import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
-import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
-import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
-import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
-import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
-import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
-import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
-import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
-import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
-import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
-import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
-import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
-import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
-import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
-import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
-import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
-import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
-import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
-import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
-import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
-import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
-import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
-import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
-import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
-import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
-import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
-import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
-import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
-import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
-import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
-import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
-import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
-import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
-import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
-import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
-import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
-import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
-import * as ep___admin_federation_refreshRemoteInstanceMetadata
-	from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
-import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
-import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
-import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
-import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
-import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
-import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
-import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
-import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
-import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
-import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
-import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
-import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
-import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
-import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
-import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
-import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
-import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
-import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
-import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
-import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
-import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
-import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
-import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
-import * as ep___admin_showUser from './endpoints/admin/show-user.js';
-import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
-import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
-import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
-import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
-import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
-import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
-import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
-import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
-import * as ep___admin_roles_list from './endpoints/admin/roles/list.js';
-import * as ep___admin_roles_show from './endpoints/admin/roles/show.js';
-import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
-import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
-import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
-import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
-import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
-import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
-import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
-import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
-import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
-import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
-import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
-import * as ep___announcements from './endpoints/announcements.js';
-import * as ep___announcements_show from './endpoints/announcements/show.js';
-import * as ep___antennas_create from './endpoints/antennas/create.js';
-import * as ep___antennas_delete from './endpoints/antennas/delete.js';
-import * as ep___antennas_list from './endpoints/antennas/list.js';
-import * as ep___antennas_notes from './endpoints/antennas/notes.js';
-import * as ep___antennas_show from './endpoints/antennas/show.js';
-import * as ep___antennas_update from './endpoints/antennas/update.js';
-import * as ep___ap_get from './endpoints/ap/get.js';
-import * as ep___ap_show from './endpoints/ap/show.js';
-import * as ep___app_create from './endpoints/app/create.js';
-import * as ep___app_show from './endpoints/app/show.js';
-import * as ep___auth_accept from './endpoints/auth/accept.js';
-import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
-import * as ep___auth_session_show from './endpoints/auth/session/show.js';
-import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
-import * as ep___blocking_create from './endpoints/blocking/create.js';
-import * as ep___blocking_delete from './endpoints/blocking/delete.js';
-import * as ep___blocking_list from './endpoints/blocking/list.js';
-import * as ep___channels_create from './endpoints/channels/create.js';
-import * as ep___channels_featured from './endpoints/channels/featured.js';
-import * as ep___channels_follow from './endpoints/channels/follow.js';
-import * as ep___channels_followed from './endpoints/channels/followed.js';
-import * as ep___channels_owned from './endpoints/channels/owned.js';
-import * as ep___channels_show from './endpoints/channels/show.js';
-import * as ep___channels_timeline from './endpoints/channels/timeline.js';
-import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
-import * as ep___channels_update from './endpoints/channels/update.js';
-import * as ep___channels_favorite from './endpoints/channels/favorite.js';
-import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
-import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
-import * as ep___channels_search from './endpoints/channels/search.js';
-import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
-import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
-import * as ep___charts_drive from './endpoints/charts/drive.js';
-import * as ep___charts_federation from './endpoints/charts/federation.js';
-import * as ep___charts_instance from './endpoints/charts/instance.js';
-import * as ep___charts_notes from './endpoints/charts/notes.js';
-import * as ep___charts_user_drive from './endpoints/charts/user/drive.js';
-import * as ep___charts_user_following from './endpoints/charts/user/following.js';
-import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
-import * as ep___charts_user_pv from './endpoints/charts/user/pv.js';
-import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
-import * as ep___charts_users from './endpoints/charts/users.js';
-import * as ep___clips_addNote from './endpoints/clips/add-note.js';
-import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
-import * as ep___clips_create from './endpoints/clips/create.js';
-import * as ep___clips_delete from './endpoints/clips/delete.js';
-import * as ep___clips_list from './endpoints/clips/list.js';
-import * as ep___clips_notes from './endpoints/clips/notes.js';
-import * as ep___clips_show from './endpoints/clips/show.js';
-import * as ep___clips_update from './endpoints/clips/update.js';
-import * as ep___clips_favorite from './endpoints/clips/favorite.js';
-import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
-import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
-import * as ep___drive from './endpoints/drive.js';
-import * as ep___drive_files from './endpoints/drive/files.js';
-import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
-import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js';
-import * as ep___drive_files_create from './endpoints/drive/files/create.js';
-import * as ep___drive_files_delete from './endpoints/drive/files/delete.js';
-import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js';
-import * as ep___drive_files_find from './endpoints/drive/files/find.js';
-import * as ep___drive_files_show from './endpoints/drive/files/show.js';
-import * as ep___drive_files_update from './endpoints/drive/files/update.js';
-import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js';
-import * as ep___drive_folders from './endpoints/drive/folders.js';
-import * as ep___drive_folders_create from './endpoints/drive/folders/create.js';
-import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js';
-import * as ep___drive_folders_find from './endpoints/drive/folders/find.js';
-import * as ep___drive_folders_show from './endpoints/drive/folders/show.js';
-import * as ep___drive_folders_update from './endpoints/drive/folders/update.js';
-import * as ep___drive_stream from './endpoints/drive/stream.js';
-import * as ep___emailAddress_available from './endpoints/email-address/available.js';
-import * as ep___endpoint from './endpoints/endpoint.js';
-import * as ep___endpoints from './endpoints/endpoints.js';
-import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js';
-import * as ep___federation_followers from './endpoints/federation/followers.js';
-import * as ep___federation_following from './endpoints/federation/following.js';
-import * as ep___federation_instances from './endpoints/federation/instances.js';
-import * as ep___federation_showInstance from './endpoints/federation/show-instance.js';
-import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js';
-import * as ep___federation_users from './endpoints/federation/users.js';
-import * as ep___federation_stats from './endpoints/federation/stats.js';
-import * as ep___following_create from './endpoints/following/create.js';
-import * as ep___following_delete from './endpoints/following/delete.js';
-import * as ep___following_update from './endpoints/following/update.js';
-import * as ep___following_update_all from './endpoints/following/update-all.js';
-import * as ep___following_invalidate from './endpoints/following/invalidate.js';
-import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
-import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
-import * as ep___following_requests_list from './endpoints/following/requests/list.js';
-import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
-import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
-import * as ep___gallery_featured from './endpoints/gallery/featured.js';
-import * as ep___gallery_popular from './endpoints/gallery/popular.js';
-import * as ep___gallery_posts from './endpoints/gallery/posts.js';
-import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js';
-import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js';
-import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js';
-import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
-import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
-import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
-import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
-import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
-import * as ep___hashtags_list from './endpoints/hashtags/list.js';
-import * as ep___hashtags_search from './endpoints/hashtags/search.js';
-import * as ep___hashtags_show from './endpoints/hashtags/show.js';
-import * as ep___hashtags_trend from './endpoints/hashtags/trend.js';
-import * as ep___hashtags_users from './endpoints/hashtags/users.js';
-import * as ep___i from './endpoints/i.js';
-import * as ep___i_2fa_done from './endpoints/i/2fa/done.js';
-import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
-import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
-import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
-import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
-import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
-import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
-import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
-import * as ep___i_apps from './endpoints/i/apps.js';
-import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js';
-import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js';
-import * as ep___i_changePassword from './endpoints/i/change-password.js';
-import * as ep___i_deleteAccount from './endpoints/i/delete-account.js';
-import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
-import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
-import * as ep___i_exportMute from './endpoints/i/export-mute.js';
-import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
-import * as ep___i_exportClips from './endpoints/i/export-clips.js';
-import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
-import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
-import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
-import * as ep___i_favorites from './endpoints/i/favorites.js';
-import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
-import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
-import * as ep___i_importFollowing from './endpoints/i/import-following.js';
-import * as ep___i_importMuting from './endpoints/i/import-muting.js';
-import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
-import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
-import * as ep___i_notifications from './endpoints/i/notifications.js';
-import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js';
-import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
-import * as ep___i_pages from './endpoints/i/pages.js';
-import * as ep___i_pin from './endpoints/i/pin.js';
-import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
-import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
-import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
-import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js';
-import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js';
-import * as ep___i_registry_get from './endpoints/i/registry/get.js';
-import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js';
-import * as ep___i_registry_keys from './endpoints/i/registry/keys.js';
-import * as ep___i_registry_remove from './endpoints/i/registry/remove.js';
-import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js';
-import * as ep___i_registry_set from './endpoints/i/registry/set.js';
-import * as ep___i_revokeToken from './endpoints/i/revoke-token.js';
-import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
-import * as ep___i_unpin from './endpoints/i/unpin.js';
-import * as ep___i_updateEmail from './endpoints/i/update-email.js';
-import * as ep___i_update from './endpoints/i/update.js';
-import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
-import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
-import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
-import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
-import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
-import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
-import * as ep___invite_create from './endpoints/invite/create.js';
-import * as ep___invite_delete from './endpoints/invite/delete.js';
-import * as ep___invite_list from './endpoints/invite/list.js';
-import * as ep___invite_limit from './endpoints/invite/limit.js';
-import * as ep___meta from './endpoints/meta.js';
-import * as ep___emojis from './endpoints/emojis.js';
-import * as ep___emoji from './endpoints/emoji.js';
-import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
-import * as ep___mute_create from './endpoints/mute/create.js';
-import * as ep___mute_delete from './endpoints/mute/delete.js';
-import * as ep___mute_list from './endpoints/mute/list.js';
-import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
-import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
-import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
-import * as ep___my_apps from './endpoints/my/apps.js';
-import * as ep___notes from './endpoints/notes.js';
-import * as ep___notes_children from './endpoints/notes/children.js';
-import * as ep___notes_clips from './endpoints/notes/clips.js';
-import * as ep___notes_conversation from './endpoints/notes/conversation.js';
-import * as ep___notes_create from './endpoints/notes/create.js';
-import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
-import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
-import * as ep___notes_featured from './endpoints/notes/featured.js';
-import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
-import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
-import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
-import * as ep___notes_mentions from './endpoints/notes/mentions.js';
-import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
-import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
-import * as ep___notes_reactions from './endpoints/notes/reactions.js';
-import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
-import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
-import * as ep___notes_renotes from './endpoints/notes/renotes.js';
-import * as ep___notes_replies from './endpoints/notes/replies.js';
-import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
-import * as ep___notes_search from './endpoints/notes/search.js';
-import * as ep___notes_show from './endpoints/notes/show.js';
-import * as ep___notes_state from './endpoints/notes/state.js';
-import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js';
-import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
-import * as ep___notes_timeline from './endpoints/notes/timeline.js';
-import * as ep___notes_translate from './endpoints/notes/translate.js';
-import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
-import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
-import * as ep___notifications_create from './endpoints/notifications/create.js';
-import * as ep___notifications_flush from './endpoints/notifications/flush.js';
-import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
-import * as ep___pagePush from './endpoints/page-push.js';
-import * as ep___pages_create from './endpoints/pages/create.js';
-import * as ep___pages_delete from './endpoints/pages/delete.js';
-import * as ep___pages_featured from './endpoints/pages/featured.js';
-import * as ep___pages_like from './endpoints/pages/like.js';
-import * as ep___pages_show from './endpoints/pages/show.js';
-import * as ep___pages_unlike from './endpoints/pages/unlike.js';
-import * as ep___pages_update from './endpoints/pages/update.js';
-import * as ep___flash_create from './endpoints/flash/create.js';
-import * as ep___flash_delete from './endpoints/flash/delete.js';
-import * as ep___flash_featured from './endpoints/flash/featured.js';
-import * as ep___flash_like from './endpoints/flash/like.js';
-import * as ep___flash_show from './endpoints/flash/show.js';
-import * as ep___flash_unlike from './endpoints/flash/unlike.js';
-import * as ep___flash_update from './endpoints/flash/update.js';
-import * as ep___flash_my from './endpoints/flash/my.js';
-import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
-import * as ep___ping from './endpoints/ping.js';
-import * as ep___pinnedUsers from './endpoints/pinned-users.js';
-import * as ep___promo_read from './endpoints/promo/read.js';
-import * as ep___roles_list from './endpoints/roles/list.js';
-import * as ep___roles_show from './endpoints/roles/show.js';
-import * as ep___roles_users from './endpoints/roles/users.js';
-import * as ep___roles_notes from './endpoints/roles/notes.js';
-import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
-import * as ep___resetDb from './endpoints/reset-db.js';
-import * as ep___resetPassword from './endpoints/reset-password.js';
-import * as ep___serverInfo from './endpoints/server-info.js';
-import * as ep___stats from './endpoints/stats.js';
-import * as ep___sw_show_registration from './endpoints/sw/show-registration.js';
-import * as ep___sw_update_registration from './endpoints/sw/update-registration.js';
-import * as ep___sw_register from './endpoints/sw/register.js';
-import * as ep___sw_unregister from './endpoints/sw/unregister.js';
-import * as ep___test from './endpoints/test.js';
-import * as ep___username_available from './endpoints/username/available.js';
-import * as ep___users from './endpoints/users.js';
-import * as ep___users_clips from './endpoints/users/clips.js';
-import * as ep___users_followers from './endpoints/users/followers.js';
-import * as ep___users_following from './endpoints/users/following.js';
-import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
-import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
-import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
-import * as ep___users_lists_create from './endpoints/users/lists/create.js';
-import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
-import * as ep___users_lists_list from './endpoints/users/lists/list.js';
-import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
-import * as ep___users_lists_push from './endpoints/users/lists/push.js';
-import * as ep___users_lists_show from './endpoints/users/lists/show.js';
-import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
-import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
-import * as ep___users_lists_update from './endpoints/users/lists/update.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
-import * as ep___users_notes from './endpoints/users/notes.js';
-import * as ep___users_pages from './endpoints/users/pages.js';
-import * as ep___users_flashs from './endpoints/users/flashs.js';
-import * as ep___users_reactions from './endpoints/users/reactions.js';
-import * as ep___users_recommendation from './endpoints/users/recommendation.js';
-import * as ep___users_relation from './endpoints/users/relation.js';
-import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
-import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
-import * as ep___users_search from './endpoints/users/search.js';
-import * as ep___users_show from './endpoints/users/show.js';
-import * as ep___users_achievements from './endpoints/users/achievements.js';
-import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
-import * as ep___fetchRss from './endpoints/fetch-rss.js';
-import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
-import * as ep___retention from './endpoints/retention.js';
-import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
-import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
-import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
-import * as ep___reversi_games from './endpoints/reversi/games.js';
-import * as ep___reversi_match from './endpoints/reversi/match.js';
-import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
-import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
-import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
-import * as ep___reversi_verify from './endpoints/reversi/verify.js';
-
-const eps = [
-	['admin/meta', ep___admin_meta],
-	['admin/abuse-user-reports', ep___admin_abuseUserReports],
-	['admin/abuse-report/notification-recipient/list', ep___admin_abuseReport_notificationRecipient_list],
-	['admin/abuse-report/notification-recipient/show', ep___admin_abuseReport_notificationRecipient_show],
-	['admin/abuse-report/notification-recipient/create', ep___admin_abuseReport_notificationRecipient_create],
-	['admin/abuse-report/notification-recipient/update', ep___admin_abuseReport_notificationRecipient_update],
-	['admin/abuse-report/notification-recipient/delete', ep___admin_abuseReport_notificationRecipient_delete],
-	['admin/accounts/create', ep___admin_accounts_create],
-	['admin/accounts/delete', ep___admin_accounts_delete],
-	['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
-	['admin/ad/create', ep___admin_ad_create],
-	['admin/ad/delete', ep___admin_ad_delete],
-	['admin/ad/list', ep___admin_ad_list],
-	['admin/ad/update', ep___admin_ad_update],
-	['admin/announcements/create', ep___admin_announcements_create],
-	['admin/announcements/delete', ep___admin_announcements_delete],
-	['admin/announcements/list', ep___admin_announcements_list],
-	['admin/announcements/update', ep___admin_announcements_update],
-	['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
-	['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
-	['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
-	['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
-	['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
-	['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
-	['admin/unset-user-banner', ep___admin_unsetUserBanner],
-	['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
-	['admin/drive/cleanup', ep___admin_drive_cleanup],
-	['admin/drive/files', ep___admin_drive_files],
-	['admin/drive/show-file', ep___admin_drive_showFile],
-	['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
-	['admin/emoji/add', ep___admin_emoji_add],
-	['admin/emoji/copy', ep___admin_emoji_copy],
-	['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
-	['admin/emoji/delete', ep___admin_emoji_delete],
-	['admin/emoji/import-zip', ep___admin_emoji_importZip],
-	['admin/emoji/list-remote', ep___admin_emoji_listRemote],
-	['admin/emoji/list', ep___admin_emoji_list],
-	['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
-	['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
-	['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
-	['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
-	['admin/emoji/update', ep___admin_emoji_update],
-	['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
-	['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
-	['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
-	['admin/federation/update-instance', ep___admin_federation_updateInstance],
-	['admin/get-index-stats', ep___admin_getIndexStats],
-	['admin/get-table-stats', ep___admin_getTableStats],
-	['admin/get-user-ips', ep___admin_getUserIps],
-	['admin/invite/create', ep___admin_invite_create],
-	['admin/invite/list', ep___admin_invite_list],
-	['admin/promo/create', ep___admin_promo_create],
-	['admin/queue/clear', ep___admin_queue_clear],
-	['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
-	['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
-	['admin/queue/promote', ep___admin_queue_promote],
-	['admin/queue/stats', ep___admin_queue_stats],
-	['admin/relays/add', ep___admin_relays_add],
-	['admin/relays/list', ep___admin_relays_list],
-	['admin/relays/remove', ep___admin_relays_remove],
-	['admin/reset-password', ep___admin_resetPassword],
-	['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport],
-	['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport],
-	['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport],
-	['admin/send-email', ep___admin_sendEmail],
-	['admin/server-info', ep___admin_serverInfo],
-	['admin/show-moderation-logs', ep___admin_showModerationLogs],
-	['admin/show-user', ep___admin_showUser],
-	['admin/show-users', ep___admin_showUsers],
-	['admin/suspend-user', ep___admin_suspendUser],
-	['admin/unsuspend-user', ep___admin_unsuspendUser],
-	['admin/update-meta', ep___admin_updateMeta],
-	['admin/delete-account', ep___admin_deleteAccount],
-	['admin/update-user-note', ep___admin_updateUserNote],
-	['admin/roles/create', ep___admin_roles_create],
-	['admin/roles/delete', ep___admin_roles_delete],
-	['admin/roles/list', ep___admin_roles_list],
-	['admin/roles/show', ep___admin_roles_show],
-	['admin/roles/update', ep___admin_roles_update],
-	['admin/roles/assign', ep___admin_roles_assign],
-	['admin/roles/unassign', ep___admin_roles_unassign],
-	['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies],
-	['admin/roles/users', ep___admin_roles_users],
-	['admin/system-webhook/create', ep___admin_systemWebhook_create],
-	['admin/system-webhook/delete', ep___admin_systemWebhook_delete],
-	['admin/system-webhook/list', ep___admin_systemWebhook_list],
-	['admin/system-webhook/show', ep___admin_systemWebhook_show],
-	['admin/system-webhook/update', ep___admin_systemWebhook_update],
-	['admin/system-webhook/test', ep___admin_systemWebhook_test],
-	['announcements', ep___announcements],
-	['announcements/show', ep___announcements_show],
-	['antennas/create', ep___antennas_create],
-	['antennas/delete', ep___antennas_delete],
-	['antennas/list', ep___antennas_list],
-	['antennas/notes', ep___antennas_notes],
-	['antennas/show', ep___antennas_show],
-	['antennas/update', ep___antennas_update],
-	['ap/get', ep___ap_get],
-	['ap/show', ep___ap_show],
-	['app/create', ep___app_create],
-	['app/show', ep___app_show],
-	['auth/accept', ep___auth_accept],
-	['auth/session/generate', ep___auth_session_generate],
-	['auth/session/show', ep___auth_session_show],
-	['auth/session/userkey', ep___auth_session_userkey],
-	['blocking/create', ep___blocking_create],
-	['blocking/delete', ep___blocking_delete],
-	['blocking/list', ep___blocking_list],
-	['channels/create', ep___channels_create],
-	['channels/featured', ep___channels_featured],
-	['channels/follow', ep___channels_follow],
-	['channels/followed', ep___channels_followed],
-	['channels/owned', ep___channels_owned],
-	['channels/show', ep___channels_show],
-	['channels/timeline', ep___channels_timeline],
-	['channels/unfollow', ep___channels_unfollow],
-	['channels/update', ep___channels_update],
-	['channels/favorite', ep___channels_favorite],
-	['channels/unfavorite', ep___channels_unfavorite],
-	['channels/my-favorites', ep___channels_myFavorites],
-	['channels/search', ep___channels_search],
-	['charts/active-users', ep___charts_activeUsers],
-	['charts/ap-request', ep___charts_apRequest],
-	['charts/drive', ep___charts_drive],
-	['charts/federation', ep___charts_federation],
-	['charts/instance', ep___charts_instance],
-	['charts/notes', ep___charts_notes],
-	['charts/user/drive', ep___charts_user_drive],
-	['charts/user/following', ep___charts_user_following],
-	['charts/user/notes', ep___charts_user_notes],
-	['charts/user/pv', ep___charts_user_pv],
-	['charts/user/reactions', ep___charts_user_reactions],
-	['charts/users', ep___charts_users],
-	['clips/add-note', ep___clips_addNote],
-	['clips/remove-note', ep___clips_removeNote],
-	['clips/create', ep___clips_create],
-	['clips/delete', ep___clips_delete],
-	['clips/list', ep___clips_list],
-	['clips/notes', ep___clips_notes],
-	['clips/show', ep___clips_show],
-	['clips/update', ep___clips_update],
-	['clips/favorite', ep___clips_favorite],
-	['clips/unfavorite', ep___clips_unfavorite],
-	['clips/my-favorites', ep___clips_myFavorites],
-	['drive', ep___drive],
-	['drive/files', ep___drive_files],
-	['drive/files/attached-notes', ep___drive_files_attachedNotes],
-	['drive/files/check-existence', ep___drive_files_checkExistence],
-	['drive/files/create', ep___drive_files_create],
-	['drive/files/delete', ep___drive_files_delete],
-	['drive/files/find-by-hash', ep___drive_files_findByHash],
-	['drive/files/find', ep___drive_files_find],
-	['drive/files/show', ep___drive_files_show],
-	['drive/files/update', ep___drive_files_update],
-	['drive/files/upload-from-url', ep___drive_files_uploadFromUrl],
-	['drive/folders', ep___drive_folders],
-	['drive/folders/create', ep___drive_folders_create],
-	['drive/folders/delete', ep___drive_folders_delete],
-	['drive/folders/find', ep___drive_folders_find],
-	['drive/folders/show', ep___drive_folders_show],
-	['drive/folders/update', ep___drive_folders_update],
-	['drive/stream', ep___drive_stream],
-	['email-address/available', ep___emailAddress_available],
-	['endpoint', ep___endpoint],
-	['endpoints', ep___endpoints],
-	['export-custom-emojis', ep___exportCustomEmojis],
-	['federation/followers', ep___federation_followers],
-	['federation/following', ep___federation_following],
-	['federation/instances', ep___federation_instances],
-	['federation/show-instance', ep___federation_showInstance],
-	['federation/update-remote-user', ep___federation_updateRemoteUser],
-	['federation/users', ep___federation_users],
-	['federation/stats', ep___federation_stats],
-	['following/create', ep___following_create],
-	['following/delete', ep___following_delete],
-	['following/update', ep___following_update],
-	['following/update-all', ep___following_update_all],
-	['following/invalidate', ep___following_invalidate],
-	['following/requests/accept', ep___following_requests_accept],
-	['following/requests/cancel', ep___following_requests_cancel],
-	['following/requests/list', ep___following_requests_list],
-	['following/requests/sent', ep___following_requests_sent],
-	['following/requests/reject', ep___following_requests_reject],
-	['gallery/featured', ep___gallery_featured],
-	['gallery/popular', ep___gallery_popular],
-	['gallery/posts', ep___gallery_posts],
-	['gallery/posts/create', ep___gallery_posts_create],
-	['gallery/posts/delete', ep___gallery_posts_delete],
-	['gallery/posts/like', ep___gallery_posts_like],
-	['gallery/posts/show', ep___gallery_posts_show],
-	['gallery/posts/unlike', ep___gallery_posts_unlike],
-	['gallery/posts/update', ep___gallery_posts_update],
-	['get-online-users-count', ep___getOnlineUsersCount],
-	['get-avatar-decorations', ep___getAvatarDecorations],
-	['hashtags/list', ep___hashtags_list],
-	['hashtags/search', ep___hashtags_search],
-	['hashtags/show', ep___hashtags_show],
-	['hashtags/trend', ep___hashtags_trend],
-	['hashtags/users', ep___hashtags_users],
-	['i', ep___i],
-	['i/2fa/done', ep___i_2fa_done],
-	['i/2fa/key-done', ep___i_2fa_keyDone],
-	['i/2fa/password-less', ep___i_2fa_passwordLess],
-	['i/2fa/register-key', ep___i_2fa_registerKey],
-	['i/2fa/register', ep___i_2fa_register],
-	['i/2fa/update-key', ep___i_2fa_updateKey],
-	['i/2fa/remove-key', ep___i_2fa_removeKey],
-	['i/2fa/unregister', ep___i_2fa_unregister],
-	['i/apps', ep___i_apps],
-	['i/authorized-apps', ep___i_authorizedApps],
-	['i/claim-achievement', ep___i_claimAchievement],
-	['i/change-password', ep___i_changePassword],
-	['i/delete-account', ep___i_deleteAccount],
-	['i/export-blocking', ep___i_exportBlocking],
-	['i/export-following', ep___i_exportFollowing],
-	['i/export-mute', ep___i_exportMute],
-	['i/export-notes', ep___i_exportNotes],
-	['i/export-clips', ep___i_exportClips],
-	['i/export-favorites', ep___i_exportFavorites],
-	['i/export-user-lists', ep___i_exportUserLists],
-	['i/export-antennas', ep___i_exportAntennas],
-	['i/favorites', ep___i_favorites],
-	['i/gallery/likes', ep___i_gallery_likes],
-	['i/gallery/posts', ep___i_gallery_posts],
-	['i/import-blocking', ep___i_importBlocking],
-	['i/import-following', ep___i_importFollowing],
-	['i/import-muting', ep___i_importMuting],
-	['i/import-user-lists', ep___i_importUserLists],
-	['i/import-antennas', ep___i_importAntennas],
-	['i/notifications', ep___i_notifications],
-	['i/notifications-grouped', ep___i_notificationsGrouped],
-	['i/page-likes', ep___i_pageLikes],
-	['i/pages', ep___i_pages],
-	['i/pin', ep___i_pin],
-	['i/read-all-unread-notes', ep___i_readAllUnreadNotes],
-	['i/read-announcement', ep___i_readAnnouncement],
-	['i/regenerate-token', ep___i_regenerateToken],
-	['i/registry/get-all', ep___i_registry_getAll],
-	['i/registry/get-detail', ep___i_registry_getDetail],
-	['i/registry/get', ep___i_registry_get],
-	['i/registry/keys-with-type', ep___i_registry_keysWithType],
-	['i/registry/keys', ep___i_registry_keys],
-	['i/registry/remove', ep___i_registry_remove],
-	['i/registry/scopes-with-domain', ep___i_registry_scopesWithDomain],
-	['i/registry/set', ep___i_registry_set],
-	['i/revoke-token', ep___i_revokeToken],
-	['i/signin-history', ep___i_signinHistory],
-	['i/unpin', ep___i_unpin],
-	['i/update-email', ep___i_updateEmail],
-	['i/update', ep___i_update],
-	['i/move', ep___i_move],
-	['i/webhooks/create', ep___i_webhooks_create],
-	['i/webhooks/list', ep___i_webhooks_list],
-	['i/webhooks/show', ep___i_webhooks_show],
-	['i/webhooks/update', ep___i_webhooks_update],
-	['i/webhooks/delete', ep___i_webhooks_delete],
-	['i/webhooks/test', ep___i_webhooks_test],
-	['invite/create', ep___invite_create],
-	['invite/delete', ep___invite_delete],
-	['invite/list', ep___invite_list],
-	['invite/limit', ep___invite_limit],
-	['meta', ep___meta],
-	['emojis', ep___emojis],
-	['emoji', ep___emoji],
-	['miauth/gen-token', ep___miauth_genToken],
-	['mute/create', ep___mute_create],
-	['mute/delete', ep___mute_delete],
-	['mute/list', ep___mute_list],
-	['renote-mute/create', ep___renoteMute_create],
-	['renote-mute/delete', ep___renoteMute_delete],
-	['renote-mute/list', ep___renoteMute_list],
-	['my/apps', ep___my_apps],
-	['notes', ep___notes],
-	['notes/children', ep___notes_children],
-	['notes/clips', ep___notes_clips],
-	['notes/conversation', ep___notes_conversation],
-	['notes/create', ep___notes_create],
-	['notes/delete', ep___notes_delete],
-	['notes/favorites/create', ep___notes_favorites_create],
-	['notes/favorites/delete', ep___notes_favorites_delete],
-	['notes/featured', ep___notes_featured],
-	['notes/global-timeline', ep___notes_globalTimeline],
-	['notes/hybrid-timeline', ep___notes_hybridTimeline],
-	['notes/local-timeline', ep___notes_localTimeline],
-	['notes/mentions', ep___notes_mentions],
-	['notes/polls/recommendation', ep___notes_polls_recommendation],
-	['notes/polls/vote', ep___notes_polls_vote],
-	['notes/reactions', ep___notes_reactions],
-	['notes/reactions/create', ep___notes_reactions_create],
-	['notes/reactions/delete', ep___notes_reactions_delete],
-	['notes/renotes', ep___notes_renotes],
-	['notes/replies', ep___notes_replies],
-	['notes/search-by-tag', ep___notes_searchByTag],
-	['notes/search', ep___notes_search],
-	['notes/show', ep___notes_show],
-	['notes/state', ep___notes_state],
-	['notes/thread-muting/create', ep___notes_threadMuting_create],
-	['notes/thread-muting/delete', ep___notes_threadMuting_delete],
-	['notes/timeline', ep___notes_timeline],
-	['notes/translate', ep___notes_translate],
-	['notes/unrenote', ep___notes_unrenote],
-	['notes/user-list-timeline', ep___notes_userListTimeline],
-	['notifications/create', ep___notifications_create],
-	['notifications/flush', ep___notifications_flush],
-	['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
-	['notifications/test-notification', ep___notifications_testNotification],
-	['page-push', ep___pagePush],
-	['pages/create', ep___pages_create],
-	['pages/delete', ep___pages_delete],
-	['pages/featured', ep___pages_featured],
-	['pages/like', ep___pages_like],
-	['pages/show', ep___pages_show],
-	['pages/unlike', ep___pages_unlike],
-	['pages/update', ep___pages_update],
-	['flash/create', ep___flash_create],
-	['flash/delete', ep___flash_delete],
-	['flash/featured', ep___flash_featured],
-	['flash/like', ep___flash_like],
-	['flash/show', ep___flash_show],
-	['flash/unlike', ep___flash_unlike],
-	['flash/update', ep___flash_update],
-	['flash/my', ep___flash_my],
-	['flash/my-likes', ep___flash_myLikes],
-	['ping', ep___ping],
-	['pinned-users', ep___pinnedUsers],
-	['promo/read', ep___promo_read],
-	['roles/list', ep___roles_list],
-	['roles/show', ep___roles_show],
-	['roles/users', ep___roles_users],
-	['roles/notes', ep___roles_notes],
-	['request-reset-password', ep___requestResetPassword],
-	['reset-db', ep___resetDb],
-	['reset-password', ep___resetPassword],
-	['server-info', ep___serverInfo],
-	['stats', ep___stats],
-	['sw/show-registration', ep___sw_show_registration],
-	['sw/update-registration', ep___sw_update_registration],
-	['sw/register', ep___sw_register],
-	['sw/unregister', ep___sw_unregister],
-	['test', ep___test],
-	['username/available', ep___username_available],
-	['users', ep___users],
-	['users/clips', ep___users_clips],
-	['users/followers', ep___users_followers],
-	['users/following', ep___users_following],
-	['users/gallery/posts', ep___users_gallery_posts],
-	['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
-	['users/featured-notes', ep___users_featuredNotes],
-	['users/lists/create', ep___users_lists_create],
-	['users/lists/delete', ep___users_lists_delete],
-	['users/lists/list', ep___users_lists_list],
-	['users/lists/pull', ep___users_lists_pull],
-	['users/lists/push', ep___users_lists_push],
-	['users/lists/show', ep___users_lists_show],
-	['users/lists/favorite', ep___users_lists_favorite],
-	['users/lists/unfavorite', ep___users_lists_unfavorite],
-	['users/lists/update', ep___users_lists_update],
-	['users/lists/create-from-public', ep___users_lists_createFromPublic],
-	['users/lists/update-membership', ep___users_lists_updateMembership],
-	['users/lists/get-memberships', ep___users_lists_getMemberships],
-	['users/notes', ep___users_notes],
-	['users/pages', ep___users_pages],
-	['users/flashs', ep___users_flashs],
-	['users/reactions', ep___users_reactions],
-	['users/recommendation', ep___users_recommendation],
-	['users/relation', ep___users_relation],
-	['users/report-abuse', ep___users_reportAbuse],
-	['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
-	['users/search', ep___users_search],
-	['users/show', ep___users_show],
-	['users/achievements', ep___users_achievements],
-	['users/update-memo', ep___users_updateMemo],
-	['fetch-rss', ep___fetchRss],
-	['fetch-external-resources', ep___fetchExternalResources],
-	['retention', ep___retention],
-	['bubble-game/register', ep___bubbleGame_register],
-	['bubble-game/ranking', ep___bubbleGame_ranking],
-	['reversi/cancel-match', ep___reversi_cancelMatch],
-	['reversi/games', ep___reversi_games],
-	['reversi/match', ep___reversi_match],
-	['reversi/invitations', ep___reversi_invitations],
-	['reversi/show-game', ep___reversi_showGame],
-	['reversi/surrender', ep___reversi_surrender],
-	['reversi/verify', ep___reversi_verify],
-];
+import * as endpointsObject from './endpoint-list.js';
 
 interface IEndpointMetaBase {
 	readonly stability?: 'deprecated' | 'experimental' | 'stable';
@@ -906,7 +130,7 @@ export interface IEndpoint {
 	params: Schema;
 }
 
-const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
+const endpoints: IEndpoint[] = Object.entries(endpointsObject).map(([name, ep]) => {
 	return {
 		name: name,
 		get meta() {
diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/current.ts b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts
new file mode 100644
index 000000000000..63ec740348d9
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
+
+export const meta = {
+	tags: ['admin', 'captcha'],
+
+	requireCredential: true,
+	requireAdmin: true,
+
+	// 実態はmetaの取得であるため
+	kind: 'read:admin:meta',
+
+	res: {
+		type: 'object',
+		properties: {
+			provider: {
+				type: 'string',
+				enum: supportedCaptchaProviders,
+			},
+			hcaptcha: {
+				type: 'object',
+				properties: {
+					siteKey: { type: 'string', nullable: true },
+					secretKey: { type: 'string', nullable: true },
+				},
+			},
+			mcaptcha: {
+				type: 'object',
+				properties: {
+					siteKey: { type: 'string', nullable: true },
+					secretKey: { type: 'string', nullable: true },
+					instanceUrl: { type: 'string', nullable: true },
+				},
+			},
+			recaptcha: {
+				type: 'object',
+				properties: {
+					siteKey: { type: 'string', nullable: true },
+					secretKey: { type: 'string', nullable: true },
+				},
+			},
+			turnstile: {
+				type: 'object',
+				properties: {
+					siteKey: { type: 'string', nullable: true },
+					secretKey: { type: 'string', nullable: true },
+				},
+			},
+		},
+	},
+} as const;
+
+export const paramDef = {} as const;
+
+@Injectable()
+export default class extends Endpoint { // eslint-disable-line import/no-default-export
+	constructor(
+		private captchaService: CaptchaService,
+	) {
+		super(meta, paramDef, async () => {
+			return this.captchaService.get();
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/save.ts b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts
new file mode 100644
index 000000000000..98ec278ebefe
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { captchaErrorCodes, CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+	tags: ['admin', 'captcha'],
+
+	requireCredential: true,
+	requireAdmin: true,
+
+	// 実態はmetaの更新であるため
+	kind: 'write:admin:meta',
+
+	errors: {
+		invalidProvider: {
+			message: 'Invalid provider.',
+			code: 'INVALID_PROVIDER',
+			id: '14bf7ae1-80cc-4363-acb2-4fd61d086af0',
+			httpStatusCode: 400,
+		},
+		invalidParameters: {
+			message: 'Invalid parameters.',
+			code: 'INVALID_PARAMETERS',
+			id: '26654194-410e-44e2-b42e-460ff6f92476',
+			httpStatusCode: 400,
+		},
+		noResponseProvided: {
+			message: 'No response provided.',
+			code: 'NO_RESPONSE_PROVIDED',
+			id: '40acbba8-0937-41fb-bb3f-474514d40afe',
+			httpStatusCode: 400,
+		},
+		requestFailed: {
+			message: 'Request failed.',
+			code: 'REQUEST_FAILED',
+			id: '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd',
+			httpStatusCode: 500,
+		},
+		verificationFailed: {
+			message: 'Verification failed.',
+			code: 'VERIFICATION_FAILED',
+			id: 'c41c067f-24f3-4150-84b2-b5a3ae8c2214',
+			httpStatusCode: 400,
+		},
+		unknown: {
+			message: 'unknown',
+			code: 'UNKNOWN',
+			id: 'f868d509-e257-42a9-99c1-42614b031a97',
+			httpStatusCode: 500,
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		provider: {
+			type: 'string',
+			enum: supportedCaptchaProviders,
+		},
+		captchaResult: {
+			type: 'string', nullable: true,
+		},
+		sitekey: {
+			type: 'string', nullable: true,
+		},
+		secret: {
+			type: 'string', nullable: true,
+		},
+		instanceUrl: {
+			type: 'string', nullable: true,
+		},
+	},
+	required: ['provider'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint { // eslint-disable-line import/no-default-export
+	constructor(
+		private captchaService: CaptchaService,
+	) {
+		super(meta, paramDef, async (ps) => {
+			const result = await this.captchaService.save(ps.provider, {
+				sitekey: ps.sitekey,
+				secret: ps.secret,
+				instanceUrl: ps.instanceUrl,
+				captchaResult: ps.captchaResult,
+			});
+
+			if (!result.success) {
+				switch (result.error.code) {
+					case captchaErrorCodes.invalidProvider:
+						throw new ApiError({
+							...meta.errors.invalidProvider,
+							message: result.error.message,
+						});
+					case captchaErrorCodes.invalidParameters:
+						throw new ApiError({
+							...meta.errors.invalidParameters,
+							message: result.error.message,
+						});
+					case captchaErrorCodes.noResponseProvided:
+						throw new ApiError({
+							...meta.errors.noResponseProvided,
+							message: result.error.message,
+						});
+					case captchaErrorCodes.requestFailed:
+						throw new ApiError({
+							...meta.errors.requestFailed,
+							message: result.error.message,
+						});
+					case captchaErrorCodes.verificationFailed:
+						throw new ApiError({
+							...meta.errors.verificationFailed,
+							message: result.error.message,
+						});
+					default:
+						throw new ApiError(meta.errors.unknown);
+				}
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 796f273330fb..53256565f6f3 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -9,6 +9,7 @@ import type { DriveFilesRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { FILE_TYPE_IMAGE } from '@/const.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -24,6 +25,11 @@ export const meta = {
 			code: 'NO_SUCH_FILE',
 			id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
 		},
+		unsupportedFileType: {
+			message: 'Unsupported file type.',
+			code: 'UNSUPPORTED_FILE_TYPE',
+			id: 'f7599d96-8750-af68-1633-9575d625c1a7',
+		},
 		duplicateName: {
 			message: 'Duplicate name.',
 			code: 'DUPLICATE_NAME',
@@ -47,15 +53,21 @@ export const paramDef = {
 			nullable: true,
 			description: 'Use `null` to reset the category.',
 		},
-		aliases: { 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',
-		} },
+		roleIdsThatCanBeUsedThisEmojiAsReaction: {
+			type: 'array',
+			items: {
+				type: 'string',
+			},
+		},
 	},
 	required: ['name', 'fileId'],
 } as const;
@@ -67,9 +79,7 @@ export default class extends Endpoint { // eslint-
 	constructor(
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
-
 		private customEmojiService: CustomEmojiService,
-
 		private emojiEntityService: EmojiEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -77,9 +87,12 @@ export default class extends Endpoint { // eslint-
 			if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
 			const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
 			if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
+			if (!FILE_TYPE_IMAGE.includes(driveFile.type)) throw new ApiError(meta.errors.unsupportedFileType);
 
 			const emoji = await this.customEmojiService.add({
-				driveFile,
+				originalUrl: driveFile.url,
+				publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+				fileType: driveFile.webpublicType ?? driveFile.type,
 				name: ps.name,
 				category: ps.category ?? null,
 				aliases: ps.aliases ?? [],
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 975f892df9b6..87b58ff6f689 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -86,7 +86,9 @@ export default class extends Endpoint { // eslint-
 			if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
 
 			const addedEmoji = await this.customEmojiService.add({
-				driveFile,
+				originalUrl: driveFile.url,
+				publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+				fileType: driveFile.webpublicType ?? driveFile.type,
 				name: emoji.name,
 				category: emoji.category,
 				aliases: emoji.aliases,
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 212cba5c5dd9..e3aaa051c1ee 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -79,13 +79,15 @@ export default class extends Endpoint { // eslint-
 			}
 
 			// JSON schemeのanyOfの型変換がうまくいっていないらしい
-			const required = { id: ps.id, name: ps.name } as 
+			const required = { id: ps.id, name: ps.name } as
 				| { id: MiEmoji['id']; name?: string }
 				| { id?: MiEmoji['id']; name: string };
 
 			const error = await this.customEmojiService.update({
 				...required,
-				driveFile,
+				originalUrl: driveFile != null ? driveFile.url : undefined,
+				publicUrl: driveFile != null ? (driveFile.webpublicUrl ?? driveFile.url) : undefined,
+				fileType: driveFile != null ? (driveFile.webpublicType ?? driveFile.type) : undefined,
 				category: ps.category,
 				aliases: ps.aliases,
 				license: ps.license,
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 24d5a7b0f1f5..5c2e82da8823 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -19,6 +19,7 @@ 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';
 
 export const meta = {
 	tags: ['federation'],
@@ -32,6 +33,31 @@ export const meta = {
 	},
 
 	errors: {
+		federationNotAllowed: {
+			message: 'Federation for this host is not allowed.',
+			code: 'FEDERATION_NOT_ALLOWED',
+			id: '974b799e-1a29-4889-b706-18d4dd93e266',
+		},
+		uriInvalid: {
+			message: 'URI is invalid.',
+			code: 'URI_INVALID',
+			id: '1a5eab56-e47b-48c2-8d5e-217b897d70db',
+		},
+		requestFailed: {
+			message: 'Request failed.',
+			code: 'REQUEST_FAILED',
+			id: '81b539cf-4f57-4b29-bc98-032c33c0792e',
+		},
+		responseInvalid: {
+			message: 'Response from remote server is invalid.',
+			code: 'RESPONSE_INVALID',
+			id: '70193c39-54f3-4813-82f0-70a680f7495b',
+		},
+		responseInvalidIdHostNotMatch: {
+			message: 'Requested URI and response URI host does not match.',
+			code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
+			id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
+		},
 		noSuchObject: {
 			message: 'No such object.',
 			code: 'NO_SUCH_OBJECT',
@@ -110,7 +136,9 @@ export default class extends Endpoint { // eslint-
 	 */
 	@bindThis
 	private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise | null> {
-		if (!this.utilityService.isFederationAllowedUri(uri)) return null;
+		if (!this.utilityService.isFederationAllowedUri(uri)) {
+			throw new ApiError(meta.errors.federationNotAllowed);
+		}
 
 		let local = await this.mergePack(me, ...await Promise.all([
 			this.apDbResolverService.getUserFromApId(uri),
@@ -125,7 +153,40 @@ export default class extends Endpoint { // eslint-
 
 		// リモートから一旦オブジェクトフェッチ
 		const resolver = this.apResolverService.createResolver();
-		const object = await resolver.resolve(uri) as any;
+		const object = await resolver.resolve(uri).catch((err) => {
+			if (err instanceof IdentifiableError) {
+				switch (err.id) {
+					// resolve
+					case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
+						throw new ApiError(meta.errors.uriInvalid);
+					case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
+					case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
+						throw new ApiError(meta.errors.requestFailed);
+					case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
+						throw new ApiError(meta.errors.federationNotAllowed);
+					case '72180409-793c-4973-868e-5a118eb5519b':
+					case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
+						throw new ApiError(meta.errors.responseInvalid);
+					case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
+						throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
+
+					// resolveLocal
+					case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
+						throw new ApiError(meta.errors.uriInvalid);
+					case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
+					case '06ae3170-1796-4d93-a697-2611ea6d83b6':
+						throw new ApiError(meta.errors.noSuchObject);
+					case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
+						throw new ApiError(meta.errors.responseInvalid);
+				}
+			}
+
+			throw new ApiError(meta.errors.requestFailed);
+		});
+
+		if (object.id == null) {
+			throw new ApiError(meta.errors.responseInvalid);
+		}
 
 		// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
 		// これはDBに存在する可能性があるため再度DB検索
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index 91c8597b1bd2..055b5cc061d1 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -87,7 +87,7 @@ export default class extends Endpoint { // eslint-
 				name: token.name ?? token.app?.name,
 				createdAt: this.idService.parse(token.id).date.toISOString(),
 				lastUsedAt: token.lastUsedAt?.toISOString(),
-				permission: token.permission,
+				permission: token.app ? token.app.permission : token.permission,
 			})));
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index d3eeb75b27c7..4c72879b737b 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -553,7 +553,7 @@ export default class extends Endpoint { // eslint-
 			const html = await this.httpRequestService.getHtml(url);
 
 			const { window } = new JSDOM(html);
-			const doc = window.document;
+			const doc: Document = window.document;
 
 			const myLink = `${this.config.url}/@${user.username}`;
 
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index f11bbbcb1a47..e52d9c32df8f 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -102,15 +102,17 @@ export default class extends Endpoint { // eslint-
 				}
 			}
 
-			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 (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);
+					}
+				});
+			}
 
 			await this.pagesRepository.update(page.id, {
 				updatedAt: new Date(),
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
new file mode 100644
index 000000000000..9426318e3461
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
@@ -0,0 +1,126 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+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';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireRolePolicy: 'canManageCustomEmojis',
+	kind: 'read:admin:emoji',
+
+	res: {
+		type: 'object',
+		properties: {
+			emojis: {
+				type: 'array',
+				items: {
+					type: 'object',
+					ref: 'EmojiDetailedAdmin',
+				},
+			},
+			count: { type: 'integer' },
+			allCount: { type: 'integer' },
+			allPages: { type: 'integer' },
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		query: {
+			type: 'object',
+			nullable: true,
+			properties: {
+				updatedAtFrom: { type: 'string' },
+				updatedAtTo: { type: 'string' },
+				name: { type: 'string' },
+				host: { type: 'string' },
+				uri: { type: 'string' },
+				publicUrl: { type: 'string' },
+				originalUrl: { type: 'string' },
+				type: { type: 'string' },
+				aliases: { type: 'string' },
+				category: { type: 'string' },
+				license: { type: 'string' },
+				isSensitive: { type: 'boolean' },
+				localOnly: { type: 'boolean' },
+				hostType: {
+					type: 'string',
+					enum: fetchEmojisHostTypes,
+					default: 'all',
+				},
+				roleIds: {
+					type: 'array',
+					items: { type: 'string', format: 'misskey:id' },
+				},
+			},
+		},
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		page: { type: 'integer' },
+		sortKeys: {
+			type: 'array',
+			default: ['-id'],
+			items: {
+				type: 'string',
+				enum: fetchEmojisSortKeys,
+			},
+		},
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint { // eslint-disable-line import/no-default-export
+	constructor(
+		private customEmojiService: CustomEmojiService,
+		private emojiEntityService: EmojiEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const q = ps.query;
+			const result = await this.customEmojiService.fetchEmojis(
+				{
+					query: {
+						updatedAtFrom: q?.updatedAtFrom,
+						updatedAtTo: q?.updatedAtTo,
+						name: q?.name,
+						host: q?.host,
+						uri: q?.uri,
+						publicUrl: q?.publicUrl,
+						type: q?.type,
+						aliases: q?.aliases,
+						category: q?.category,
+						license: q?.license,
+						isSensitive: q?.isSensitive,
+						localOnly: q?.localOnly,
+						hostType: q?.hostType,
+						roleIds: q?.roleIds,
+					},
+					sinceId: ps.sinceId,
+					untilId: ps.untilId,
+				},
+				{
+					limit: ps.limit,
+					page: ps.page,
+					sortKeys: ps.sortKeys,
+				},
+			);
+
+			return {
+				emojis: await this.emojiEntityService.packDetailedAdminMany(result.emojis),
+				count: result.count,
+				allCount: result.allCount,
+				allPages: result.allPages,
+			};
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index efa47a698624..3b20ec1321da 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -183,7 +183,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
 				},
 				...(endpoint.meta.limit ? {
 					'429': {
-						description: 'To many requests',
+						description: 'Too many requests',
 						content: {
 							'application/json': {
 								schema: {
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index eb854a71414f..c80dda8d965a 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -3,13 +3,15 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { deepClone } from '@/misc/clone.js';
 import type { Schema } from '@/misc/json-schema.js';
 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 { optional, nullable, ref, selfRef, ..._res }: any = schema;
+	const res = deepClone(_res);
 
 	if (schema.type === 'object' && schema.properties) {
 		if (type === 'res') {
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 75bd13221f10..568131149318 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -95,7 +95,6 @@ class HybridTimelineChannel extends Channel {
 
 		if (this.user && note.renoteId && !note.text) {
 			if (note.renote && Object.keys(note.renote.reactions).length > 0) {
-				console.log(note.renote.reactionAndUserPairCache);
 				const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
 				note.renote.myReaction = myRenoteReaction;
 			}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 1b8873214b4f..4c884dd31469 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -317,16 +317,19 @@ export class ClientServerService {
 				done();
 			});
 		} else {
+			const configUrl = new URL(this.config.url);
+			const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, '');
+
 			const port = (process.env.VITE_PORT ?? '5173');
 			fastify.register(fastifyProxy, {
-				upstream: 'http://localhost:' + port,
+				upstream: urlOriginWithoutPort + ':' + port,
 				prefix: '/vite',
 				rewritePrefix: '/vite',
 			});
 
 			const embedPort = (process.env.EMBED_VITE_PORT ?? '5174');
 			fastify.register(fastifyProxy, {
-				upstream: 'http://localhost:' + embedPort,
+				upstream: urlOriginWithoutPort + ':' + embedPort,
 				prefix: '/embed_vite',
 				rewritePrefix: '/embed_vite',
 			});
@@ -509,6 +512,7 @@ export class ClientServerService {
 				usernameLower: username.toLowerCase(),
 				host: host ?? IsNull(),
 				isSuspended: false,
+				requireSigninToViewContents: false,
 			});
 
 			return user && await this.feedService.packFeed(user);
@@ -585,7 +589,10 @@ export class ClientServerService {
 					reply.header('X-Robots-Tag', 'noai');
 				}
 
-				const _user = await this.userEntityService.pack(user);
+				const _user = await this.userEntityService.pack(user, null, {
+					schema: 'UserDetailed',
+					userProfile: profile,
+				});
 
 				return await reply.view('user', {
 					user, profile, me,
@@ -868,7 +875,7 @@ export class ClientServerService {
 			});
 
 			if (note == null) return;
-			if (note.visibility !== 'public') return;
+			if (['specified', 'followers'].includes(note.visibility)) return;
 			if (note.userHost != null) return;
 
 			const _note = await this.noteEntityService.pack(note, null, { detail: true });
diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts
index bacc4cc54f18..220c22e198cd 100644
--- a/packages/backend/test-federation/test/note.test.ts
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -131,11 +131,7 @@ describe('Note', () => {
 			rejects(
 				async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
 				(err: any) => {
-					/**
-					 * FIXME: this error is not handled
-					 * @see https://github.com/misskey-dev/misskey/issues/12736
-					 */
-					strictEqual(err.code, 'INTERNAL_ERROR');
+					strictEqual(err.code, 'REQUEST_FAILED');
 					return true;
 				},
 			);
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index d12be2a9acdc..319c8581f40b 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -397,7 +397,7 @@ describe('Timelines', () => {
 			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 * 10);
+		}, 1000 * 15);
 
 		test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup()]);
diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts
index 235af29f0dc9..1326003c5e81 100644
--- a/packages/backend/test/unit/AbuseReportNotificationService.ts
+++ b/packages/backend/test/unit/AbuseReportNotificationService.ts
@@ -3,13 +3,14 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { jest } from '@jest/globals';
+import { describe, jest } from '@jest/globals';
 import { Test, TestingModule } from '@nestjs/testing';
 import { randomString } from '../utils.js';
 import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
 import {
 	AbuseReportNotificationRecipientRepository,
 	MiAbuseReportNotificationRecipient,
+	MiAbuseUserReport,
 	MiSystemWebhook,
 	MiUser,
 	SystemWebhooksRepository,
@@ -112,7 +113,10 @@ describe('AbuseReportNotificationService', () => {
 						provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }),
 					},
 					{
-						provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }),
+						provide: UserEntityService, useFactory: () => ({
+							pack: (v: any) => Promise.resolve(v),
+							packMany: (v: any) => Promise.resolve(v),
+						}),
 					},
 					{
 						provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
@@ -344,4 +348,46 @@ describe('AbuseReportNotificationService', () => {
 			expect(recipients).toEqual([recipient3]);
 		});
 	});
+
+	describe('notifySystemWebhook', () => {
+		test('非アクティブな通報通知はWebhook送信から除外される', async () => {
+			const recipient1 = await createRecipient({
+				method: 'webhook',
+				systemWebhookId: systemWebhook1.id,
+				isActive: true,
+			});
+			const recipient2 = await createRecipient({
+				method: 'webhook',
+				systemWebhookId: systemWebhook2.id,
+				isActive: false,
+			});
+
+			const reports: MiAbuseUserReport[] = [
+				{
+					id: idService.gen(),
+					targetUserId: alice.id,
+					targetUser: alice,
+					reporterId: bob.id,
+					reporter: bob,
+					assigneeId: null,
+					assignee: null,
+					resolved: false,
+					forwarded: false,
+					comment: 'test',
+					moderationNote: '',
+					resolvedAs: null,
+					targetUserHost: null,
+					reporterHost: null,
+				},
+			];
+
+			await service.notifySystemWebhook(reports, 'abuseReport');
+
+			// 実際に除外されるかはSystemWebhookService側で確認する.
+			// ここでは非アクティブな通報通知を除外設定できているかを確認する
+			expect(webhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+			expect(webhookService.enqueueSystemWebhook.mock.calls[0][0]).toBe('abuseReport');
+			expect(webhookService.enqueueSystemWebhook.mock.calls[0][2]).toEqual({ excludes: [systemWebhook2.id] });
+		});
+	});
 });
diff --git a/packages/backend/test/unit/CaptchaService.ts b/packages/backend/test/unit/CaptchaService.ts
new file mode 100644
index 000000000000..51b70b05a17c
--- /dev/null
+++ b/packages/backend/test/unit/CaptchaService.ts
@@ -0,0 +1,622 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterAll, beforeAll, beforeEach, describe, expect, jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { Response } from 'node-fetch';
+import {
+	CaptchaError,
+	CaptchaErrorCode,
+	captchaErrorCodes,
+	CaptchaSaveResult,
+	CaptchaService,
+} from '@/core/CaptchaService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { HttpRequestService } from '@/core/HttpRequestService.js';
+import { MetaService } from '@/core/MetaService.js';
+import { MiMeta } from '@/models/Meta.js';
+import { LoggerService } from '@/core/LoggerService.js';
+
+describe('CaptchaService', () => {
+	let app: TestingModule;
+	let service: CaptchaService;
+	let httpRequestService: jest.Mocked;
+	let metaService: jest.Mocked;
+
+	beforeAll(async () => {
+		app = await Test.createTestingModule({
+			imports: [
+				GlobalModule,
+			],
+			providers: [
+				CaptchaService,
+				LoggerService,
+				{
+					provide: HttpRequestService, useFactory: () => ({ send: jest.fn() }),
+				},
+				{
+					provide: MetaService, useFactory: () => ({
+						fetch: jest.fn(),
+						update: jest.fn(),
+					}),
+				},
+			],
+		}).compile();
+
+		app.enableShutdownHooks();
+
+		service = app.get(CaptchaService);
+		httpRequestService = app.get(HttpRequestService) as jest.Mocked;
+		metaService = app.get(MetaService) as jest.Mocked;
+	});
+
+	beforeEach(() => {
+		httpRequestService.send.mockClear();
+		metaService.update.mockClear();
+		metaService.fetch.mockClear();
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	function successMock(result: object) {
+		httpRequestService.send.mockResolvedValue({
+			ok: true,
+			status: 200,
+			json: async () => (result),
+		} as Response);
+	}
+
+	function failureHttpMock() {
+		httpRequestService.send.mockResolvedValue({
+			ok: false,
+			status: 400,
+		} as Response);
+	}
+
+	function failureVerificationMock(result: object) {
+		httpRequestService.send.mockResolvedValue({
+			ok: true,
+			status: 200,
+			json: async () => (result),
+		} as Response);
+	}
+
+	async function testCaptchaError(code: CaptchaErrorCode, test: () => Promise) {
+		try {
+			await test();
+			expect(false).toBe(true);
+		} catch (e) {
+			expect(e instanceof CaptchaError).toBe(true);
+
+			const _e = e as CaptchaError;
+			expect(_e.code).toBe(code);
+		}
+	}
+
+	describe('verifyRecaptcha', () => {
+		test('success', async () => {
+			successMock({ success: true });
+			await service.verifyRecaptcha('secret', 'response');
+		});
+
+		test('noResponseProvided', async () => {
+			await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyRecaptcha('secret', null));
+		});
+
+		test('requestFailed', async () => {
+			failureHttpMock();
+			await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyRecaptcha('secret', 'response'));
+		});
+
+		test('verificationFailed', async () => {
+			failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+			await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyRecaptcha('secret', 'response'));
+		});
+	});
+
+	describe('verifyHcaptcha', () => {
+		test('success', async () => {
+			successMock({ success: true });
+			await service.verifyHcaptcha('secret', 'response');
+		});
+
+		test('noResponseProvided', async () => {
+			await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyHcaptcha('secret', null));
+		});
+
+		test('requestFailed', async () => {
+			failureHttpMock();
+			await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyHcaptcha('secret', 'response'));
+		});
+
+		test('verificationFailed', async () => {
+			failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+			await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyHcaptcha('secret', 'response'));
+		});
+	});
+
+	describe('verifyMcaptcha', () => {
+		const host = 'https://localhost';
+
+		test('success', async () => {
+			successMock({ valid: true });
+			await service.verifyMcaptcha('secret', 'sitekey', host, 'response');
+		});
+
+		test('noResponseProvided', async () => {
+			await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyMcaptcha('secret', 'sitekey', host, null));
+		});
+
+		test('requestFailed', async () => {
+			failureHttpMock();
+			await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+		});
+
+		test('verificationFailed', async () => {
+			failureVerificationMock({ valid: false });
+			await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+		});
+	});
+
+	describe('verifyTurnstile', () => {
+		test('success', async () => {
+			successMock({ success: true });
+			await service.verifyTurnstile('secret', 'response');
+		});
+
+		test('noResponseProvided', async () => {
+			await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTurnstile('secret', null));
+		});
+
+		test('requestFailed', async () => {
+			failureHttpMock();
+			await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyTurnstile('secret', 'response'));
+		});
+
+		test('verificationFailed', async () => {
+			failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+			await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTurnstile('secret', 'response'));
+		});
+	});
+
+	describe('verifyTestcaptcha', () => {
+		test('success', async () => {
+			await service.verifyTestcaptcha('testcaptcha-passed');
+		});
+
+		test('noResponseProvided', async () => {
+			await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTestcaptcha(null));
+		});
+
+		test('verificationFailed', async () => {
+			await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTestcaptcha('testcaptcha-failed'));
+		});
+	});
+
+	describe('get', () => {
+		function setupMeta(meta: Partial) {
+			metaService.fetch.mockResolvedValue(meta as MiMeta);
+		}
+
+		test('values', async () => {
+			setupMeta({
+				enableHcaptcha: false,
+				enableMcaptcha: false,
+				enableRecaptcha: false,
+				enableTurnstile: false,
+				enableTestcaptcha: false,
+				hcaptchaSiteKey: 'hcaptcha-sitekey',
+				hcaptchaSecretKey: 'hcaptcha-secret',
+				mcaptchaSitekey: 'mcaptcha-sitekey',
+				mcaptchaSecretKey: 'mcaptcha-secret',
+				mcaptchaInstanceUrl: 'https://localhost',
+				recaptchaSiteKey: 'recaptcha-sitekey',
+				recaptchaSecretKey: 'recaptcha-secret',
+				turnstileSiteKey: 'turnstile-sitekey',
+				turnstileSecretKey: 'turnstile-secret',
+			});
+
+			const result = await service.get();
+			expect(result.provider).toBe('none');
+			expect(result.hcaptcha.siteKey).toBe('hcaptcha-sitekey');
+			expect(result.hcaptcha.secretKey).toBe('hcaptcha-secret');
+			expect(result.mcaptcha.siteKey).toBe('mcaptcha-sitekey');
+			expect(result.mcaptcha.secretKey).toBe('mcaptcha-secret');
+			expect(result.mcaptcha.instanceUrl).toBe('https://localhost');
+			expect(result.recaptcha.siteKey).toBe('recaptcha-sitekey');
+			expect(result.recaptcha.secretKey).toBe('recaptcha-secret');
+			expect(result.turnstile.siteKey).toBe('turnstile-sitekey');
+			expect(result.turnstile.secretKey).toBe('turnstile-secret');
+		});
+
+		describe('provider', () => {
+			test('none', async () => {
+				setupMeta({
+					enableHcaptcha: false,
+					enableMcaptcha: false,
+					enableRecaptcha: false,
+					enableTurnstile: false,
+					enableTestcaptcha: false,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('none');
+			});
+
+			test('hcaptcha', async () => {
+				setupMeta({
+					enableHcaptcha: true,
+					enableMcaptcha: false,
+					enableRecaptcha: false,
+					enableTurnstile: false,
+					enableTestcaptcha: false,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('hcaptcha');
+			});
+
+			test('mcaptcha', async () => {
+				setupMeta({
+					enableHcaptcha: false,
+					enableMcaptcha: true,
+					enableRecaptcha: false,
+					enableTurnstile: false,
+					enableTestcaptcha: false,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('mcaptcha');
+			});
+
+			test('recaptcha', async () => {
+				setupMeta({
+					enableHcaptcha: false,
+					enableMcaptcha: false,
+					enableRecaptcha: true,
+					enableTurnstile: false,
+					enableTestcaptcha: false,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('recaptcha');
+			});
+
+			test('turnstile', async () => {
+				setupMeta({
+					enableHcaptcha: false,
+					enableMcaptcha: false,
+					enableRecaptcha: false,
+					enableTurnstile: true,
+					enableTestcaptcha: false,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('turnstile');
+			});
+
+			test('testcaptcha', async () => {
+				setupMeta({
+					enableHcaptcha: false,
+					enableMcaptcha: false,
+					enableRecaptcha: false,
+					enableTurnstile: false,
+					enableTestcaptcha: true,
+				});
+
+				const result = await service.get();
+				expect(result.provider).toBe('testcaptcha');
+			});
+		});
+	});
+
+	describe('save', () => {
+		const host = 'https://localhost';
+
+		describe('[success] 検証に成功した時だけ保存できる+他のプロバイダの設定値を誤って更新しない', () => {
+			beforeEach(() => {
+				successMock({ success: true, valid: true });
+			});
+
+			async function assertSuccess(promise: Promise, expectMeta: Partial) {
+				await expect(promise)
+					.resolves
+					.toStrictEqual({ success: true });
+				const partialParams = metaService.update.mock.calls[0][0];
+				expect(partialParams).toStrictEqual(expectMeta);
+			}
+
+			test('none', async () => {
+				await assertSuccess(
+					service.save('none'),
+					{
+						enableHcaptcha: false,
+						enableMcaptcha: false,
+						enableRecaptcha: false,
+						enableTurnstile: false,
+						enableTestcaptcha: false,
+					},
+				);
+			});
+
+			test('hcaptcha', async () => {
+				await assertSuccess(
+					service.save('hcaptcha', {
+						sitekey: 'hcaptcha-sitekey',
+						secret: 'hcaptcha-secret',
+						captchaResult: 'hcaptcha-passed',
+					}),
+					{
+						enableHcaptcha: true,
+						enableMcaptcha: false,
+						enableRecaptcha: false,
+						enableTurnstile: false,
+						enableTestcaptcha: false,
+						hcaptchaSiteKey: 'hcaptcha-sitekey',
+						hcaptchaSecretKey: 'hcaptcha-secret',
+					},
+				);
+			});
+
+			test('mcaptcha', async () => {
+				await assertSuccess(
+					service.save('mcaptcha', {
+						sitekey: 'mcaptcha-sitekey',
+						secret: 'mcaptcha-secret',
+						instanceUrl: host,
+						captchaResult: 'mcaptcha-passed',
+					}),
+					{
+						enableHcaptcha: false,
+						enableMcaptcha: true,
+						enableRecaptcha: false,
+						enableTurnstile: false,
+						enableTestcaptcha: false,
+						mcaptchaSitekey: 'mcaptcha-sitekey',
+						mcaptchaSecretKey: 'mcaptcha-secret',
+						mcaptchaInstanceUrl: host,
+					},
+				);
+			});
+
+			test('recaptcha', async () => {
+				await assertSuccess(
+					service.save('recaptcha', {
+						sitekey: 'recaptcha-sitekey',
+						secret: 'recaptcha-secret',
+						captchaResult: 'recaptcha-passed',
+					}),
+					{
+						enableHcaptcha: false,
+						enableMcaptcha: false,
+						enableRecaptcha: true,
+						enableTurnstile: false,
+						enableTestcaptcha: false,
+						recaptchaSiteKey: 'recaptcha-sitekey',
+						recaptchaSecretKey: 'recaptcha-secret',
+					},
+				);
+			});
+
+			test('turnstile', async () => {
+				await assertSuccess(
+					service.save('turnstile', {
+						sitekey: 'turnstile-sitekey',
+						secret: 'turnstile-secret',
+						captchaResult: 'turnstile-passed',
+					}),
+					{
+						enableHcaptcha: false,
+						enableMcaptcha: false,
+						enableRecaptcha: false,
+						enableTurnstile: true,
+						enableTestcaptcha: false,
+						turnstileSiteKey: 'turnstile-sitekey',
+						turnstileSecretKey: 'turnstile-secret',
+					},
+				);
+			});
+
+			test('testcaptcha', async () => {
+				await assertSuccess(
+					service.save('testcaptcha', {
+						sitekey: 'testcaptcha-sitekey',
+						secret: 'testcaptcha-secret',
+						captchaResult: 'testcaptcha-passed',
+					}),
+					{
+						enableHcaptcha: false,
+						enableMcaptcha: false,
+						enableRecaptcha: false,
+						enableTurnstile: false,
+						enableTestcaptcha: true,
+					},
+				);
+			});
+		});
+
+		describe('[failure] 検証に失敗した場合は保存できない+設定値の更新そのものが発生しない', () => {
+			async function assertFailure(code: CaptchaErrorCode, promise: Promise) {
+				const res = await promise;
+				expect(res.success).toBe(false);
+				if (!res.success) {
+					expect(res.error.code).toBe(code);
+				}
+				expect(metaService.update).not.toBeCalled();
+			}
+
+			describe('invalidParameters', () => {
+				test('hcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.invalidParameters,
+						service.save('hcaptcha', {
+							sitekey: 'hcaptcha-sitekey',
+							secret: 'hcaptcha-secret',
+							captchaResult: null,
+						}),
+					);
+				});
+
+				test('mcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.invalidParameters,
+						service.save('mcaptcha', {
+							sitekey: 'mcaptcha-sitekey',
+							secret: 'mcaptcha-secret',
+							instanceUrl: host,
+							captchaResult: null,
+						}),
+					);
+				});
+
+				test('recaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.invalidParameters,
+						service.save('recaptcha', {
+							sitekey: 'recaptcha-sitekey',
+							secret: 'recaptcha-secret',
+							captchaResult: null,
+						}),
+					);
+				});
+
+				test('turnstile', async () => {
+					await assertFailure(
+						captchaErrorCodes.invalidParameters,
+						service.save('turnstile', {
+							sitekey: 'turnstile-sitekey',
+							secret: 'turnstile-secret',
+							captchaResult: null,
+						}),
+					);
+				});
+
+				test('testcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.invalidParameters,
+						service.save('testcaptcha', {
+							captchaResult: null,
+						}),
+					);
+				});
+			});
+
+			describe('requestFailed', () => {
+				beforeEach(() => {
+					failureHttpMock();
+				});
+
+				test('hcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.requestFailed,
+						service.save('hcaptcha', {
+							sitekey: 'hcaptcha-sitekey',
+							secret: 'hcaptcha-secret',
+							captchaResult: 'hcaptcha-passed',
+						}),
+					);
+				});
+
+				test('mcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.requestFailed,
+						service.save('mcaptcha', {
+							sitekey: 'mcaptcha-sitekey',
+							secret: 'mcaptcha-secret',
+							instanceUrl: host,
+							captchaResult: 'mcaptcha-passed',
+						}),
+					);
+				});
+
+				test('recaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.requestFailed,
+						service.save('recaptcha', {
+							sitekey: 'recaptcha-sitekey',
+							secret: 'recaptcha-secret',
+							captchaResult: 'recaptcha-passed',
+						}),
+					);
+				});
+
+				test('turnstile', async () => {
+					await assertFailure(
+						captchaErrorCodes.requestFailed,
+						service.save('turnstile', {
+							sitekey: 'turnstile-sitekey',
+							secret: 'turnstile-secret',
+							captchaResult: 'turnstile-passed',
+						}),
+					);
+				});
+
+				// testchapchaはrequestFailedがない
+			});
+
+			describe('verificationFailed', () => {
+				beforeEach(() => {
+					failureVerificationMock({ success: false, valid: false, 'error-codes': ['code01', 'code02'] });
+				});
+
+				test('hcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.verificationFailed,
+						service.save('hcaptcha', {
+							sitekey: 'hcaptcha-sitekey',
+							secret: 'hcaptcha-secret',
+							captchaResult: 'hccaptcha-passed',
+						}),
+					);
+				});
+
+				test('mcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.verificationFailed,
+						service.save('mcaptcha', {
+							sitekey: 'mcaptcha-sitekey',
+							secret: 'mcaptcha-secret',
+							instanceUrl: host,
+							captchaResult: 'mcaptcha-passed',
+						}),
+					);
+				});
+
+				test('recaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.verificationFailed,
+						service.save('recaptcha', {
+							sitekey: 'recaptcha-sitekey',
+							secret: 'recaptcha-secret',
+							captchaResult: 'recaptcha-passed',
+						}),
+					);
+				});
+
+				test('turnstile', async () => {
+					await assertFailure(
+						captchaErrorCodes.verificationFailed,
+						service.save('turnstile', {
+							sitekey: 'turnstile-sitekey',
+							secret: 'turnstile-secret',
+							captchaResult: 'turnstile-passed',
+						}),
+					);
+				});
+
+				test('testcaptcha', async () => {
+					await assertFailure(
+						captchaErrorCodes.verificationFailed,
+						service.save('testcaptcha', {
+							captchaResult: 'testcaptcha-failed',
+						}),
+					);
+				});
+			});
+		});
+	});
+});
diff --git a/packages/backend/test/unit/CustomEmojiService.ts b/packages/backend/test/unit/CustomEmojiService.ts
new file mode 100644
index 000000000000..10b687c6a0a7
--- /dev/null
+++ b/packages/backend/test/unit/CustomEmojiService.ts
@@ -0,0 +1,817 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterEach, beforeAll, describe, test } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { EmojisRepository } from '@/models/_.js';
+import { MiEmoji } from '@/models/Emoji.js';
+
+describe('CustomEmojiService', () => {
+	let app: TestingModule;
+	let service: CustomEmojiService;
+
+	let emojisRepository: EmojisRepository;
+	let idService: IdService;
+
+	beforeAll(async () => {
+		app = await Test
+			.createTestingModule({
+				imports: [
+					GlobalModule,
+				],
+				providers: [
+					CustomEmojiService,
+					UtilityService,
+					IdService,
+					EmojiEntityService,
+					ModerationLogService,
+					GlobalEventService,
+				],
+			})
+			.compile();
+		app.enableShutdownHooks();
+
+		service = app.get(CustomEmojiService);
+		emojisRepository = app.get(DI.emojisRepository);
+		idService = app.get(IdService);
+	});
+
+	describe('fetchEmojis', () => {
+		async function insert(data: Partial[]) {
+			for (const d of data) {
+				const id = idService.gen();
+				await emojisRepository.insert({
+					id: id,
+					updatedAt: new Date(),
+					...d,
+				});
+			}
+		}
+
+		function call(params: Parameters['0']) {
+			return service.fetchEmojis(
+				params,
+				{
+					// テスト向けに
+					sortKeys: ['+id'],
+				},
+			);
+		}
+
+		function defaultData(suffix: string, override?: Partial): Partial {
+			return {
+				name: `emoji${suffix}`,
+				host: null,
+				category: 'default',
+				originalUrl: `https://example.com/emoji${suffix}.png`,
+				publicUrl: `https://example.com/emoji${suffix}.png`,
+				type: 'image/png',
+				aliases: [`emoji${suffix}`],
+				license: 'CC0',
+				isSensitive: false,
+				localOnly: false,
+				roleIdsThatCanBeUsedThisEmojiAsReaction: [],
+				...override,
+			};
+		}
+
+		afterEach(async () => {
+			await emojisRepository.delete({});
+		});
+
+		describe('単独', () => {
+			test('updatedAtFrom', async () => {
+				await insert([
+					defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+					defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+					defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+				]);
+
+				const actual = await call({
+					query: {
+						updatedAtFrom: '2021-01-02T00:00:00.000Z',
+					},
+				});
+
+				expect(actual.allCount).toBe(2);
+				expect(actual.emojis[0].name).toBe('emoji002');
+				expect(actual.emojis[1].name).toBe('emoji003');
+			});
+
+			test('updatedAtTo', async () => {
+				await insert([
+					defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+					defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+					defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+				]);
+
+				const actual = await call({
+					query: {
+						updatedAtTo: '2021-01-02T00:00:00.000Z',
+					},
+				});
+
+				expect(actual.allCount).toBe(2);
+				expect(actual.emojis[0].name).toBe('emoji001');
+				expect(actual.emojis[1].name).toBe('emoji002');
+			});
+
+			describe('name', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001'),
+						defaultData('002'),
+					]);
+
+					const actual = await call({
+						query: {
+							name: 'emoji001',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji001');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001'),
+						defaultData('002'),
+					]);
+
+					const actual = await call({
+						query: {
+							name: 'emoji001 emoji002',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji002');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001'),
+						defaultData('002'),
+						defaultData('003', { name: 'em003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							name: 'oji',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji002');
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001'),
+					]);
+
+					const actual = await call({
+						query: {
+							name: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('host', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { host: 'example.com' }),
+						defaultData('002', { host: 'example.com' }),
+						defaultData('003', { host: '1.example.com' }),
+						defaultData('004', { host: '2.example.com' }),
+					]);
+
+					const actual = await call({
+						query: {
+							host: 'example.com',
+							hostType: 'remote',
+						},
+					});
+
+					expect(actual.allCount).toBe(4);
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { host: 'example.com' }),
+						defaultData('002', { host: 'example.com' }),
+						defaultData('003', { host: '1.example.com' }),
+						defaultData('004', { host: '2.example.com' }),
+					]);
+
+					const actual = await call({
+						query: {
+							host: '1.example.com 2.example.com',
+							hostType: 'remote',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji003');
+					expect(actual.emojis[1].name).toBe('emoji004');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { host: 'example.com' }),
+						defaultData('002', { host: 'example.com' }),
+						defaultData('003', { host: '1.example.com' }),
+						defaultData('004', { host: '2.example.com' }),
+					]);
+
+					const actual = await call({
+						query: {
+							host: 'example',
+							hostType: 'remote',
+						},
+					});
+
+					expect(actual.allCount).toBe(4);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { host: 'example.com' }),
+					]);
+
+					const actual = await call({
+						query: {
+							host: '%',
+							hostType: 'remote',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('uri', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { uri: 'uri001' }),
+						defaultData('002', { uri: 'uri002' }),
+						defaultData('003', { uri: 'uri003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							uri: 'uri002',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { uri: 'uri001' }),
+						defaultData('002', { uri: 'uri002' }),
+						defaultData('003', { uri: 'uri003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							uri: 'uri001 uri003',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { uri: 'uri001' }),
+						defaultData('002', { uri: 'uri002' }),
+						defaultData('003', { uri: 'uri003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							uri: 'ri',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { uri: 'uri001' }),
+					]);
+
+					const actual = await call({
+						query: {
+							uri: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('publicUrl', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { publicUrl: 'publicUrl001' }),
+						defaultData('002', { publicUrl: 'publicUrl002' }),
+						defaultData('003', { publicUrl: 'publicUrl003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							publicUrl: 'publicUrl002',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { publicUrl: 'publicUrl001' }),
+						defaultData('002', { publicUrl: 'publicUrl002' }),
+						defaultData('003', { publicUrl: 'publicUrl003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							publicUrl: 'publicUrl001 publicUrl003',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { publicUrl: 'publicUrl001' }),
+						defaultData('002', { publicUrl: 'publicUrl002' }),
+						defaultData('003', { publicUrl: 'publicUrl003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							publicUrl: 'Url',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { publicUrl: 'publicUrl001' }),
+					]);
+
+					const actual = await call({
+						query: {
+							publicUrl: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('type', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { type: 'type001' }),
+						defaultData('002', { type: 'type002' }),
+						defaultData('003', { type: 'type003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							type: 'type002',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { type: 'type001' }),
+						defaultData('002', { type: 'type002' }),
+						defaultData('003', { type: 'type003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							type: 'type001 type003',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { type: 'type001' }),
+						defaultData('002', { type: 'type002' }),
+						defaultData('003', { type: 'type003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							type: 'pe',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { type: 'type001' }),
+					]);
+
+					const actual = await call({
+						query: {
+							type: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('aliases', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { aliases: ['alias001', 'alias002'] }),
+						defaultData('002', { aliases: ['alias002'] }),
+						defaultData('003', { aliases: ['alias003'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							aliases: 'alias002',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { aliases: ['alias001', 'alias002'] }),
+						defaultData('002', { aliases: ['alias002', 'alias004'] }),
+						defaultData('003', { aliases: ['alias003'] }),
+						defaultData('004', { aliases: ['alias004'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							aliases: 'alias001 alias004',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji002');
+					expect(actual.emojis[2].name).toBe('emoji004');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { aliases: ['alias001', 'alias002'] }),
+						defaultData('002', { aliases: ['alias002', 'alias004'] }),
+						defaultData('003', { aliases: ['alias003'] }),
+						defaultData('004', { aliases: ['alias004'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							aliases: 'ias',
+						},
+					});
+
+					expect(actual.allCount).toBe(4);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { aliases: ['alias001', 'alias002'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							aliases: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('category', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { category: 'category001' }),
+						defaultData('002', { category: 'category002' }),
+						defaultData('003', { category: 'category003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							category: 'category002',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { category: 'category001' }),
+						defaultData('002', { category: 'category002' }),
+						defaultData('003', { category: 'category003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							category: 'category001 category003',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { category: 'category001' }),
+						defaultData('002', { category: 'category002' }),
+						defaultData('003', { category: 'category003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							category: 'egory',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { category: 'category001' }),
+					]);
+
+					const actual = await call({
+						query: {
+							category: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('license', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { license: 'license001' }),
+						defaultData('002', { license: 'license002' }),
+						defaultData('003', { license: 'license003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							license: 'license002',
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { license: 'license001' }),
+						defaultData('002', { license: 'license002' }),
+						defaultData('003', { license: 'license003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							license: 'license001 license003',
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('keyword', async () => {
+					await insert([
+						defaultData('001', { license: 'license001' }),
+						defaultData('002', { license: 'license002' }),
+						defaultData('003', { license: 'license003' }),
+					]);
+
+					const actual = await call({
+						query: {
+							license: 'cense',
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+
+				test('escape', async () => {
+					await insert([
+						defaultData('001', { license: 'license001' }),
+					]);
+
+					const actual = await call({
+						query: {
+							license: '%',
+						},
+					});
+
+					expect(actual.allCount).toBe(0);
+				});
+			});
+
+			describe('isSensitive', () => {
+				test('true', async () => {
+					await insert([
+						defaultData('001', { isSensitive: true }),
+						defaultData('002', { isSensitive: false }),
+						defaultData('003', { isSensitive: true }),
+					]);
+
+					const actual = await call({
+						query: {
+							isSensitive: true,
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('false', async () => {
+					await insert([
+						defaultData('001', { isSensitive: true }),
+						defaultData('002', { isSensitive: false }),
+						defaultData('003', { isSensitive: true }),
+					]);
+
+					const actual = await call({
+						query: {
+							isSensitive: false,
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('null', async () => {
+					await insert([
+						defaultData('001', { isSensitive: true }),
+						defaultData('002', { isSensitive: false }),
+						defaultData('003', { isSensitive: true }),
+					]);
+
+					const actual = await call({
+						query: {},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+			});
+
+			describe('localOnly', () => {
+				test('true', async () => {
+					await insert([
+						defaultData('001', { localOnly: true }),
+						defaultData('002', { localOnly: false }),
+						defaultData('003', { localOnly: true }),
+					]);
+
+					const actual = await call({
+						query: {
+							localOnly: true,
+						},
+					});
+
+					expect(actual.allCount).toBe(2);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji003');
+				});
+
+				test('false', async () => {
+					await insert([
+						defaultData('001', { localOnly: true }),
+						defaultData('002', { localOnly: false }),
+						defaultData('003', { localOnly: true }),
+					]);
+
+					const actual = await call({
+						query: {
+							localOnly: false,
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('null', async () => {
+					await insert([
+						defaultData('001', { localOnly: true }),
+						defaultData('002', { localOnly: false }),
+						defaultData('003', { localOnly: true }),
+					]);
+
+					const actual = await call({
+						query: {},
+					});
+
+					expect(actual.allCount).toBe(3);
+				});
+			});
+
+			describe('roleId', () => {
+				test('single', async () => {
+					await insert([
+						defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+						defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002'] }),
+						defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							roleIds: ['role002'],
+						},
+					});
+
+					expect(actual.allCount).toBe(1);
+					expect(actual.emojis[0].name).toBe('emoji002');
+				});
+
+				test('multi', async () => {
+					await insert([
+						defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+						defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002', 'role003'] }),
+						defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+						defaultData('004', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role004'] }),
+					]);
+
+					const actual = await call({
+						query: {
+							roleIds: ['role001', 'role003'],
+						},
+					});
+
+					expect(actual.allCount).toBe(3);
+					expect(actual.emojis[0].name).toBe('emoji001');
+					expect(actual.emojis[1].name).toBe('emoji002');
+					expect(actual.emojis[2].name).toBe('emoji003');
+				});
+			});
+		});
+	});
+});
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index fd4a03413bb1..36af8823f6fc 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -108,6 +108,24 @@ describe('MfmService', () => {
 			assert.deepStrictEqual(mfmService.fromHtml('

a d

'), 'a d'); }); + test('ruby', () => { + assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミスキー) b

'), 'a $[ruby Misskey ミスキー] b'); + assert.deepStrictEqual(mfmService.fromHtml('

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

'), 'a $[ruby Misskey ミスキー]$[ruby Misskey ミスキー] b'); + }); + + test('ruby with spaces', () => { + assert.deepStrictEqual(mfmService.fromHtml('

a Miss key(ミスキー) b c

'), 'a Miss key(ミスキー) b c'); + 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' + ); + }); + + test('ruby with other inline tags', () => { + assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミスキー) b c

'), 'a **Misskey**(ミスキー) b c'); + }); + test('mention', () => { assert.deepStrictEqual(mfmService.fromHtml('

a @user d

'), 'a @user@example.com d'); }); diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts index 5401dd74d80b..fee4acb30596 100644 --- a/packages/backend/test/unit/SystemWebhookService.ts +++ b/packages/backend/test/unit/SystemWebhookService.ts @@ -314,9 +314,10 @@ describe('SystemWebhookService', () => { isActive: true, on: ['abuseReport'], }); - await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any); - expect(queueService.systemWebhookDeliver).toHaveBeenCalled(); + expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook); }); test('非アクティブなWebhookはキューに追加されない', async () => { @@ -324,7 +325,7 @@ describe('SystemWebhookService', () => { isActive: false, on: ['abuseReport'], }); - await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); }); @@ -338,11 +339,49 @@ describe('SystemWebhookService', () => { isActive: true, on: ['abuseReportResolved'], }); - await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any); - await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any); + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); }); + + test('混在した時、有効かつ許可されたイベント種別のみ', async () => { + const webhook1 = await createWebhook({ + isActive: true, + on: ['abuseReport'], + }); + const webhook2 = await createWebhook({ + isActive: true, + on: ['abuseReportResolved'], + }); + const webhook3 = await createWebhook({ + isActive: false, + on: ['abuseReport'], + }); + const webhook4 = await createWebhook({ + isActive: false, + on: ['abuseReportResolved'], + }); + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any); + + expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1); + }); + + test('除外指定した場合は送信されない', async () => { + const webhook1 = await createWebhook({ + isActive: true, + on: ['abuseReport'], + }); + const webhook2 = await createWebhook({ + isActive: true, + on: ['abuseReport'], + }); + + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any, { excludes: [webhook2.id] }); + + expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1); + }); }); describe('fetchActiveSystemWebhooks', () => { diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts index 0e88835a0243..db8f96df280c 100644 --- a/packages/backend/test/unit/UserWebhookService.ts +++ b/packages/backend/test/unit/UserWebhookService.ts @@ -1,4 +1,3 @@ - /* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only @@ -71,7 +70,7 @@ describe('UserWebhookService', () => { LoggerService, GlobalEventService, { - provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }), + provide: QueueService, useFactory: () => ({ userWebhookDeliver: jest.fn() }), }, ], }) @@ -242,4 +241,92 @@ describe('UserWebhookService', () => { }); }); }); + + describe('アプリを毎回作り直す必要があるグループ', () => { + beforeEach(async () => { + await beforeAllImpl(); + await beforeEachImpl(); + }); + + afterEach(async () => { + await afterEachImpl(); + await afterAllImpl(); + }); + + describe('enqueueUserWebhook', () => { + test('キューに追加成功', async () => { + const webhook = await createWebhook({ + active: true, + on: ['note'], + }); + await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any); + + expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook); + }); + + test('非アクティブなWebhookはキューに追加されない', async () => { + const webhook = await createWebhook({ + active: false, + on: ['note'], + }); + await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any); + + expect(queueService.userWebhookDeliver).not.toHaveBeenCalled(); + }); + + test('未許可のイベント種別が渡された場合はWebhookはキューに追加されない', async () => { + const webhook1 = await createWebhook({ + active: true, + on: [], + }); + const webhook2 = await createWebhook({ + active: true, + on: ['note'], + }); + await service.enqueueUserWebhook(webhook1.userId, 'renote', { foo: 'bar' } as any); + await service.enqueueUserWebhook(webhook2.userId, 'renote', { foo: 'bar' } as any); + + expect(queueService.userWebhookDeliver).not.toHaveBeenCalled(); + }); + + test('ユーザIDが異なるWebhookはキューに追加されない', async () => { + const webhook = await createWebhook({ + active: true, + on: ['note'], + }); + await service.enqueueUserWebhook(idService.gen(), 'note', { foo: 'bar' } as any); + + expect(queueService.userWebhookDeliver).not.toHaveBeenCalled(); + }); + + test('混在した時、有効かつ許可されたイベント種別のみ', async () => { + const userId = root.id; + const webhook1 = await createWebhook({ + userId, + active: true, + on: ['note'], + }); + const webhook2 = await createWebhook({ + userId, + active: true, + on: ['renote'], + }); + const webhook3 = await createWebhook({ + userId, + active: false, + on: ['note'], + }); + const webhook4 = await createWebhook({ + userId, + active: false, + on: ['renote'], + }); + await service.enqueueUserWebhook(userId, 'note', { foo: 'bar' } as any); + + expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook1); + }); + }); + }); }); diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts index 1506283a3ccd..d96e6b916a60 100644 --- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts +++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -18,6 +18,7 @@ import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; import { EmailService } from '@/core/EmailService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; +import { SystemWebhookEventType } from '@/models/SystemWebhook.js'; const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0)); @@ -334,9 +335,10 @@ describe('CheckModeratorsActivityProcessorService', () => { mockModeratorRole([user1]); await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 }); - expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2); - expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1); - expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2); + // typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する. + // ここでは呼び出されているか、typeが正しいかのみを確認する + expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1); + expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsWarning'); }); }); @@ -372,8 +374,10 @@ describe('CheckModeratorsActivityProcessorService', () => { mockModeratorRole([user1]); await service.notifyChangeToInvitationOnly(); + // typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する. + // ここでは呼び出されているか、typeが正しいかのみを確認する expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1); - expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2); + expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsInvitationOnlyChanged'); }); }); }); diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 59b744e43a3f..ab5026ab0d1b 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -4,7 +4,6 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts --debug hmr", "build": "vite build", "typecheck": "vue-tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", @@ -15,7 +14,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.7", "@rollup/pluginutils": "5.1.3", - "@tabler/icons-webfont": "3.3.0", + "@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.29.0-mi.1913+5921534bc.tar.gz", "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.2.0", "@vue/compiler-sfc": "3.5.12", @@ -25,7 +24,7 @@ "mfm-js": "0.24.0", "misskey-js": "workspace:*", "frontend-shared": "workspace:*", - "punycode": "2.3.1", + "punycode.js": "2.3.1", "rollup": "4.26.0", "sass": "1.79.4", "shiki": "1.22.2", @@ -44,7 +43,7 @@ "@types/estree": "1.0.6", "@types/micromatch": "4.0.9", "@types/node": "22.9.0", - "@types/punycode": "2.1.4", + "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", "@types/ws": "8.5.13", diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index 8ab4ab32e6f8..c1b2b58bebdf 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -17,11 +17,11 @@ 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 } from '@@/js/config.js'; +import { url, version, locale, lang, updateLocale } 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 } from '@/i18n.js'; +import { i18n, updateI18n } from '@/i18n.js'; import type { Theme } from '@/theme.js'; @@ -71,6 +71,22 @@ 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); + } +} +//#endregion + // サイズの制限 document.documentElement.style.maxWidth = '500px'; diff --git a/packages/frontend-embed/src/components/EmAcct.vue b/packages/frontend-embed/src/components/EmAcct.vue index 6856b8272e86..ff794d9b6e6b 100644 --- a/packages/frontend-embed/src/components/EmAcct.vue +++ b/packages/frontend-embed/src/components/EmAcct.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 4664ad48804e..680ab80167ec 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -75,16 +75,21 @@ function compile(theme: Theme): Record { return getColor(theme.props[val]); } else if (val[0] === ':') { // func const parts = val.split('<'); - const func = parts.shift().substring(1); - const arg = parseFloat(parts.shift()); - const color = getColor(parts.join('<')); - - switch (func) { - case 'darken': return color.darken(arg); - case 'lighten': return color.lighten(arg); - case 'alpha': return color.setAlpha(arg); - case 'hue': return color.spin(arg); - case 'saturate': return color.saturate(arg); + const funcTxt = parts.shift(); + const argTxt = parts.shift(); + + if (funcTxt && argTxt) { + const func = funcTxt.substring(1); + const arg = parseFloat(argTxt); + const color = getColor(parts.join('<')); + + switch (func) { + case 'darken': return color.darken(arg); + case 'lighten': return color.lighten(arg); + case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); + } } } diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json index 45f933dc2844..60164a7e3d37 100644 --- a/packages/frontend-embed/tsconfig.json +++ b/packages/frontend-embed/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": false, "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", + "module": "ES2022", + "moduleResolution": "Bundler", "removeComments": false, "noLib": false, "strict": true, diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts deleted file mode 100644 index bf2f478887d6..000000000000 --- a/packages/frontend-embed/vite.config.local-dev.ts +++ /dev/null @@ -1,96 +0,0 @@ -import dns from 'dns'; -import { readFile } from 'node:fs/promises'; -import type { IncomingMessage } from 'node:http'; -import { defineConfig } from 'vite'; -import type { UserConfig } from 'vite'; -import * as yaml from 'js-yaml'; -import locales from '../../locales/index.js'; -import { getConfig } from './vite.config.js'; - -dns.setDefaultResultOrder('ipv4first'); - -const defaultConfig = getConfig(); - -const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')); - -const httpUrl = `http://localhost:${port}/`; -const websocketUrl = `ws://localhost:${port}/`; - -// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す -function varyHandler(req: IncomingMessage) { - if (req.headers.accept?.includes('application/activity+json')) { - return null; - } - return '/index.html'; -} - -const devConfig: UserConfig = { - // 基本の設定は vite.config.js から引き継ぐ - ...defaultConfig, - root: 'src', - publicDir: '../assets', - base: '/embed', - server: { - host: 'localhost', - port: 5174, - proxy: { - '/api': { - changeOrigin: true, - target: httpUrl, - }, - '/assets': httpUrl, - '/static-assets': httpUrl, - '/client-assets': httpUrl, - '/files': httpUrl, - '/twemoji': httpUrl, - '/fluent-emoji': httpUrl, - '/sw.js': httpUrl, - '/streaming': { - target: websocketUrl, - ws: true, - }, - '/favicon.ico': httpUrl, - '/robots.txt': httpUrl, - '/embed.js': httpUrl, - '/identicon': { - target: httpUrl, - rewrite(path) { - return path.replace('@localhost:5173', ''); - }, - }, - '/url': httpUrl, - '/proxy': httpUrl, - '/_info_card_': httpUrl, - '/bios': httpUrl, - '/cli': httpUrl, - '/inbox': httpUrl, - '/emoji/': httpUrl, - '/notes': { - target: httpUrl, - bypass: varyHandler, - }, - '/users': { - target: httpUrl, - bypass: varyHandler, - }, - '/.well-known': { - target: httpUrl, - }, - }, - }, - build: { - ...defaultConfig.build, - rollupOptions: { - ...defaultConfig.build?.rollupOptions, - input: 'index.html', - }, - }, - - define: { - ...defaultConfig.define, - _LANGS_FULL_: JSON.stringify(Object.entries(locales)), - }, -}; - -export default defineConfig(({ command, mode }) => devConfig); - diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 2dbee488c5f0..3d628c800e43 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -1,12 +1,17 @@ import path from 'path'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; +import * as yaml from 'js-yaml'; +import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; import packageInfo from './package.json' with { type: 'json' }; import pluginJson5 from './vite.json5.js'; +const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; +const host = url ? (new URL(url)).hostname : undefined; + const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; /** @@ -62,7 +67,14 @@ export function getConfig(): UserConfig { base: '/embed_vite/', server: { + host, port: 5174, + hmr: { + // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される + // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない + // クライアント側のWSポートをViteサーバーのポートに強制させることで、正しくHMRが機能するようになる + clientPort: 5174, + }, }, plugins: [ diff --git a/packages/frontend-shared/build.js b/packages/frontend-shared/build.js index 17b6da8d30a2..994111475792 100644 --- a/packages/frontend-shared/build.js +++ b/packages/frontend-shared/build.js @@ -23,10 +23,14 @@ const options = { sourcemap: 'linked', }; +const args = process.argv.slice(2).map(arg => arg.toLowerCase()); + // js-built配下をすべて削除する -fs.rmSync('./js-built', { recursive: true, force: true }); +if (!args.includes('--no-clean')) { + fs.rmSync('./js-built', { recursive: true, force: true }); +} -if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) { +if (args.includes('--watch')) { await watchSrc(); } else { await buildSrc(); diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index f8770f33f2a5..4e681393c6bc 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -26,6 +26,7 @@ "@typescript-eslint/parser": "7.17.0", "esbuild": "0.24.0", "eslint-plugin-vue": "9.31.0", + "nodemon": "3.1.7", "typescript": "5.6.3", "vue-eslint-parser": "9.4.3" }, diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts new file mode 100644 index 000000000000..c777cbbe72ed --- /dev/null +++ b/packages/frontend/.storybook/fake-utils.ts @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import seedrandom from 'seedrandom'; + +/** + * AIで生成した無作為なファーストネーム + */ +export const firstNameDict = [ + 'Ethan', 'Olivia', 'Jackson', 'Emma', 'Liam', 'Ava', 'Aiden', 'Sophia', 'Mason', 'Isabella', + 'Noah', 'Mia', 'Lucas', 'Harper', 'Caleb', 'Abigail', 'Samuel', 'Emily', 'Logan', + 'Madison', 'Benjamin', 'Chloe', 'Elijah', 'Grace', 'Alexander', 'Scarlett', 'William', 'Zoey', 'James', 'Lily', +] + +/** + * AIで生成した無作為なラストネーム + */ +export const lastNameDict = [ + 'Anderson', 'Johnson', 'Thompson', 'Davis', 'Rodriguez', 'Smith', 'Patel', 'Williams', 'Lee', 'Brown', + 'Garcia', 'Jackson', 'Martinez', 'Taylor', 'Harris', 'Nguyen', 'Miller', 'Jones', 'Wilson', + 'White', 'Thomas', 'Garcia', 'Martinez', 'Robinson', 'Turner', 'Lewis', 'Hall', 'King', 'Baker', 'Cooper', +] + +/** + * AIで生成した無作為な国名 + */ +export const countryDict = [ + 'Japan', 'Canada', 'Brazil', 'Australia', 'Italy', 'SouthAfrica', 'Mexico', 'Sweden', 'Russia', 'India', + 'Germany', 'Argentina', 'South Korea', 'France', 'Nigeria', 'Turkey', 'Spain', 'Egypt', 'Thailand', + 'Vietnam', 'Kenya', 'Saudi Arabia', 'Netherlands', 'Colombia', 'Poland', 'Chile', 'Malaysia', 'Ukraine', 'New Zealand', 'Peru', +] + +export function text(length: number = 10, seed?: string): string { + let result = ""; + + // シード値を使う場合、同じ数値が羅列されるが、ランダム文字列という意味では満たせていると思うのでこのまま使っておく + const rand = seed ? seedrandom(seed)() : Math.random(); + while (result.length < length) { + result += rand.toString(36).substring(2); + } + + return result.substring(0, length); +} + +export function integer(min: number = 0, max: number = 9999, seed?: string): number { + const rand = seed ? seedrandom(seed)() : Math.random(); + return Math.floor(rand * (max - min)) + min; +} + +export function date(params?: { + yearMin?: number, + yearMax?: number, + monthMin?: number, + monthMax?: number, + dayMin?: number, + dayMax?: number, + hourMin?: number, + hourMax?: number, + minuteMin?: number, + minuteMax?: number, + secondMin?: number, + secondMax?: number, + millisecondMin?: number, + millisecondMax?: number, +}, seed?: string): Date { + const year = integer(params?.yearMin ?? 1970, params?.yearMax ?? (new Date()).getFullYear(), seed); + const month = integer(params?.monthMin ?? 1, params?.monthMax ?? 12, seed); + let day = integer(params?.dayMin ?? 1, params?.dayMax ?? 31, seed); + if (month === 2) { + day = Math.min(day, 28); + } else if ([4, 6, 9, 11].includes(month)) { + day = Math.min(day, 30); + } else { + day = Math.min(day, 31); + } + + const hour = integer(params?.hourMin ?? 0, params?.hourMax ?? 23, seed); + const minute = integer(params?.minuteMin ?? 0, params?.minuteMax ?? 59, seed); + const second = integer(params?.secondMin ?? 0, params?.secondMax ?? 59, seed); + const millisecond = integer(params?.millisecondMin ?? 0, params?.millisecondMax ?? 999, seed); + + return new Date(year, month - 1, day, hour, minute, second, millisecond); +} + +export function boolean(seed?: string): boolean { + const rand = seed ? seedrandom(seed)() : Math.random(); + return rand < 0.5; +} + +export function choose(array: T[], seed?: string): T { + const rand = seed ? seedrandom(seed)() : Math.random(); + return array[Math.floor(rand * array.length)]; +} + +export function firstName(seed?: string): string { + return choose(firstNameDict, seed); +} + +export function lastName(seed?: string): string { + return choose(lastNameDict, seed); +} + +export function country(seed?: string): string { + return choose(countryDict, seed); +} + +const TIME2000 = 946684800000; +export function fakeId(seed?: string): string { + let time = new Date().getTime(); + + time = time - TIME2000; + if (time < 0) time = 0; + + const timeStr = time.toString(36).padStart(8, '0'); + const noiseStr = text(2, seed); + + return timeStr + noiseStr; +} + +export function imageDataUrl(options?: { + size?: { + width?: number, + height?: number, + }, + color?: { + red?: number, + green?: number, + blue?: number, + alpha?: number, + } +}, seed?: string): string { + const canvas = document.createElement('canvas'); + canvas.width = options?.size?.width ?? 100; + canvas.height = options?.size?.height ?? 100; + + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Failed to get 2d context'); + } + + ctx.beginPath() + + const red = options?.color?.red ?? integer(0, 255, seed); + const green = options?.color?.green ?? integer(0, 255, seed); + const blue = options?.color?.blue ?? integer(0, 255, seed); + const alpha = options?.color?.alpha ?? 1; + ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true); + ctx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + ctx.fill(); + + return canvas.toDataURL('image/png', 1.0); +} diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index fc3b0334e470..0a5ac15aa574 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -5,6 +5,7 @@ import { AISCRIPT_VERSION } from '@syuilo/aiscript'; import type { entities } from 'misskey-js' +import { date, imageDataUrl, text } from "./fake-utils.js"; export function abuseUserReport() { return { @@ -301,3 +302,93 @@ export function inviteCode(isUsed = false, hasExpiration = false, isExpired = fa used: isUsed, } } + +export function role(params: { + id?: string, + name?: string, + color?: string | null, + iconUrl?: string | null, + description?: string, + isModerator?: boolean, + isAdministrator?: boolean, + displayOrder?: number, + createdAt?: string, + updatedAt?: string, + target?: 'manual' | 'conditional', + isPublic?: boolean, + isExplorable?: boolean, + asBadge?: boolean, + canEditMembersByModerator?: boolean, + usersCount?: number, +}, seed?: string): entities.Role { + const prefix = params.displayOrder ? params.displayOrder.toString().padStart(3, '0') + '-' : ''; + const genId = text(36, seed); + const createdAt = params.createdAt ?? date({}, seed).toISOString(); + const updatedAt = params.updatedAt ?? date({}, seed).toISOString(); + + return { + id: params.id ?? genId, + name: params.name ?? `${prefix}TestRole-${genId}`, + color: params.color ?? '#445566', + iconUrl: params.iconUrl ?? null, + description: params.description ?? '', + isModerator: params.isModerator ?? false, + isAdministrator: params.isAdministrator ?? false, + displayOrder: params.displayOrder ?? 0, + createdAt: createdAt, + updatedAt: updatedAt, + target: params.target ?? 'manual', + isPublic: params.isPublic ?? true, + isExplorable: params.isExplorable ?? true, + asBadge: params.asBadge ?? true, + canEditMembersByModerator: params.canEditMembersByModerator ?? false, + usersCount: params.usersCount ?? 10, + condFormula: { + id: '', + type: 'or', + values: [] + }, + policies: {}, + } +} + +export function emoji(params?: { + id?: string, + name?: string, + host?: string, + uri?: string, + publicUrl?: string, + originalUrl?: string, + type?: string, + aliases?: string[], + category?: string, + license?: string, + isSensitive?: boolean, + localOnly?: boolean, + roleIdsThatCanBeUsedThisEmojiAsReaction?: {id:string, name:string}[], + updatedAt?: string, +}, seed?: string): entities.EmojiDetailedAdmin { + const _seed = seed ?? (params?.id ?? "DEFAULT_SEED"); + const id = params?.id ?? text(32, _seed); + const name = params?.name ?? text(8, _seed); + const updatedAt = params?.updatedAt ?? date({}, _seed).toISOString(); + + const image = imageDataUrl({}, _seed) + + return { + id: id, + name: name, + host: params?.host ?? null, + uri: params?.uri ?? null, + publicUrl: params?.publicUrl ?? image, + originalUrl: params?.originalUrl ?? image, + type: params?.type ?? 'image/png', + aliases: params?.aliases ?? [`alias1-${name}`, `alias2-${name}`], + category: params?.category ?? null, + license: params?.license ?? null, + isSensitive: params?.isSensitive ?? false, + localOnly: params?.localOnly ?? false, + roleIdsThatCanBeUsedThisEmojiAsReaction: params?.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], + updatedAt: updatedAt, + } +} diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index f2bdc631d2b3..8830523810e5 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -416,6 +416,10 @@ function toStories(component: string): Promise { glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInviteCode.vue'), + glob('src/components/MkTagItem.vue'), + glob('src/components/MkRoleSelectDialog.vue'), + glob('src/components/grid/MkGrid.vue'), + glob('src/pages/admin/custom-emojis-manager2.vue'), glob('src/pages/admin/overview.ap-requests.vue'), glob('src/pages/user/home.vue'), glob('src/pages/search.vue'), diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 4f132ab50011..804160baad30 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,6 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts --debug hmr", "build": "vite build", "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", "build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", @@ -25,11 +24,11 @@ "@rollup/plugin-replace": "5.0.7", "@rollup/pluginutils": "5.1.3", "@syuilo/aiscript": "0.19.0", - "@tabler/icons-webfont": "3.3.0", + "@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.29.0-mi.1913+5921534bc.tar.gz", "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.2.0", "@vue/compiler-sfc": "3.5.12", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "astring": "1.9.0", "broadcast-channel": "7.0.0", "buraha": "0.0.1", @@ -56,7 +55,7 @@ "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", "photoswipe": "5.4.4", - "punycode": "2.3.1", + "punycode.js": "2.3.1", "rollup": "4.26.0", "sanitize-html": "2.13.1", "sass": "1.79.3", @@ -101,7 +100,7 @@ "@types/matter-js": "0.19.7", "@types/micromatch": "4.0.9", "@types/node": "22.9.0", - "@types/punycode": "2.1.4", + "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.13.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts deleted file mode 100644 index f312765dcf40..000000000000 --- a/packages/frontend/src/_dev_boot_.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -await main(); - -import('@/_boot_.js'); - -/** - * backend/src/server/web/boot.jsで差し込まれている起動処理のうち、最低限必要なものを模倣するための処理 - */ -async function main() { - const forceError = localStorage.getItem('forceError'); - if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); - } - - //#region Detect language & fetch translations - - // dev-modeの場合は常に取り直す - const supportedLangs = _LANGS_.map(it => it[0]); - let lang: string | null | undefined = 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'; - } - } - - // TODO:今のままだと言語ファイル変更後はpnpm devをリスタートする必要があるので、chokidarを使ったり等で対応できるようにする - const locale = _LANGS_FULL_.find(it => it[0] === lang); - localStorage.setItem('lang', lang); - localStorage.setItem('locale', JSON.stringify(locale[1])); - localStorage.setItem('localeVersion', _VERSION_); - //#endregion - - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); - - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; - } - } - } - } - } - const colorScheme = localStorage.getItem('colorScheme'); - if (colorScheme) { - document.documentElement.style.setProperty('color-scheme', colorScheme); - } - //#endregion - - const fontSize = localStorage.getItem('fontSize'); - if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); - } - - const useSystemFont = localStorage.getItem('useSystemFont'); - if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); - } - - const wallpaper = localStorage.getItem('wallpaper'); - if (wallpaper) { - document.documentElement.style.backgroundImage = `url(${wallpaper})`; - } - - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); - } -} - -function renderError(code: string, details?: string) { - console.log(code, details); -} diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index bfe5c4f5f708..ae6b1aee26aa 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -98,6 +98,11 @@ export async function common(createVue: () => App) { // タッチデバイスでCSSの:hoverを機能させる document.addEventListener('touchend', () => {}, { passive: true }); + // URLに#pswpを含む場合は取り除く + if (location.hash === '#pswp') { + history.replaceState(null, '', location.href.replace('#pswp', '')); + } + // 一斉リロード reloadChannel.addEventListener('message', path => { if (path !== null) location.href = path; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 2bf902947992..874e97f3a405 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -7,6 +7,7 @@ import { createApp, defineAsyncComponent, markRaw } from 'vue'; import { ui } from '@@/js/config.js'; import { common } from './common.js'; import type * as Misskey from 'misskey-js'; +import type { Component } from 'vue'; import { i18n } from '@/i18n.js'; import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; @@ -25,13 +26,38 @@ import { type Keymap, makeHotkey } from '@/scripts/hotkey.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; export async function mainBoot() { - const { isClientUpdated } = await common(() => createApp( - new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : - !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : - ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : - ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : - defineAsyncComponent(() => import('@/ui/universal.vue')), - )); + const { isClientUpdated } = await common(() => { + let uiStyle = ui; + const searchParams = new URLSearchParams(window.location.search); + + if (!$i) uiStyle = 'visitor'; + + if (searchParams.has('zen')) uiStyle = 'zen'; + if (uiStyle === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') uiStyle = 'zen'; + + if (searchParams.has('ui')) uiStyle = searchParams.get('ui'); + + let rootComponent: Component; + switch (uiStyle) { + case 'zen': + rootComponent = defineAsyncComponent(() => import('@/ui/zen.vue')); + break; + case 'deck': + rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue')); + break; + case 'visitor': + rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue')); + break; + case 'classic': + rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue')); + break; + default: + rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue')); + break; + } + + return createApp(rootComponent); + }); reactionPicker.init(); emojiPicker.init(); diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index e52ab5ccad15..365b767bd646 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -77,8 +77,8 @@ import MkPostForm from '@/components/MkPostForm.vue'; const props = withDefaults(defineProps<{ component: AsUiComponent; components: Ref[]; - size: 'small' | 'medium' | 'large'; - align: 'left' | 'center' | 'right'; + size?: 'small' | 'medium' | 'large'; + align?: 'left' | 'center' | 'right'; }>(), { size: 'medium', align: 'left', @@ -86,7 +86,7 @@ const props = withDefaults(defineProps<{ const c = props.component; -function g(id) { +function g(id: string) { const v = props.components.find(x => x.value.id === id)?.value; if (v) return v; @@ -122,13 +122,22 @@ const containerStyle = computed(() => { const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false); -function onSwitchUpdate(v) { +function onSwitchUpdate(v: boolean) { valueForSwitch.value = v; if ('onChange' in c && c.onChange) { c.onChange(v as never); } } +const valueForSelect = ref('default' in c && typeof c.default !== 'boolean' ? c.default ?? null : null); + +function onSelectUpdate(v) { + valueForSelect.value = v; + if ('onChange' in c && c.onChange) { + c.onChange(v as never); + } +} + function openPostForm() { const form = (c as AsUiPostFormButton).form; if (!form) return; diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index 264cf9af069b..b1167bbac654 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -30,6 +30,9 @@ import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmount import { defaultStore } from '@/store.js'; // APIs provided by Captcha services +// see: https://docs.hcaptcha.com/configuration/#javascript-api +// see: https://developers.google.com/recaptcha/docs/display?hl=ja +// see: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#explicitly-render-the-turnstile-widget export type Captcha = { render(container: string | Node, options: { readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown; @@ -53,6 +56,7 @@ declare global { const props = defineProps<{ provider: CaptchaProvider; sitekey: string | null; // null will show error on request + secretKey?: string | null; instanceUrl?: string | null; modelValue?: string | null; }>(); @@ -64,7 +68,7 @@ const emit = defineEmits<{ const available = ref(false); const captchaEl = shallowRef(); - +const captchaWidgetId = ref(undefined); const testcaptchaInput = ref(''); const testcaptchaPassed = ref(false); @@ -94,6 +98,15 @@ const scriptId = computed(() => `script-${props.provider}`); const captcha = computed(() => window[variable.value] || {} as unknown as Captcha); +watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => { + // 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない + if (available.value) { + callback(undefined); + clearWidget(); + await requestRender(); + } +}); + if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { available.value = true; } else if (src.value !== null) { @@ -106,14 +119,38 @@ if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') } function reset() { - if (captcha.value.reset) captcha.value.reset(); + if (captcha.value.reset && captchaWidgetId.value !== undefined) { + try { + captcha.value.reset(captchaWidgetId.value); + } catch (error: unknown) { + // ignore + if (_DEV_) console.warn(error); + } + } testcaptchaPassed.value = false; testcaptchaInput.value = ''; } +function remove() { + if (captcha.value.remove && captchaWidgetId.value) { + try { + if (_DEV_) console.log('remove', props.provider, captchaWidgetId.value); + captcha.value.remove(captchaWidgetId.value); + } catch (error: unknown) { + // ignore + if (_DEV_) console.warn(error); + } + } +} + async function requestRender() { - if (captcha.value.render && captchaEl.value instanceof Element) { - captcha.value.render(captchaEl.value, { + if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) { + // reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する. + // (同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので) + const elem = document.createElement('div'); + captchaEl.value.appendChild(elem); + + captchaWidgetId.value = captcha.value.render(elem, { sitekey: props.sitekey, theme: defaultStore.state.darkMode ? 'dark' : 'light', callback: callback, @@ -133,6 +170,23 @@ async function requestRender() { } } +function clearWidget() { + if (props.provider === 'mcaptcha') { + const container = document.getElementById('mcaptcha__widget-container'); + if (container) { + container.innerHTML = ''; + } + } else { + reset(); + remove(); + + if (captchaEl.value) { + // レンダリング先のコンテナの中身を掃除し、フォームが増殖するのを抑止 + captchaEl.value.innerHTML = ''; + } + } +} + function callback(response?: string) { emit('update:modelValue', typeof response === 'string' ? response : null); } @@ -165,7 +219,7 @@ onUnmounted(() => { }); onBeforeUnmount(() => { - reset(); + clearWidget(); }); defineExpose({ diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index c470042b79ac..f79989d88254 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -125,7 +125,9 @@ const bannerStyle = computed(() => { position: absolute; top: 16px; left: 16px; + max-width: calc(100% - 32px); padding: 12px 16px; + box-sizing: border-box; background: rgba(0, 0, 0, 0.7); color: #fff; font-size: 1.2em; diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index f513795c5619..6a278250fa9e 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only >
-
@@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{ thin?: boolean; naked?: boolean; foldable?: boolean; + onUnfold?: () => boolean; // return false to prevent unfolding scrollable?: boolean; expanded?: boolean; maxHeight?: number | null; @@ -101,6 +102,13 @@ const omitObserver = new ResizeObserver((entries, observer) => { calcOmit(); }); +function showMore() { + if (props.onUnfold && !props.onUnfold()) return; + + ignoreOmit.value = true; + omitted.value = false; +} + onMounted(() => { watch(showBody, v => { if (!rootEl.value) return; diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index 3410a915c300..874d9b04cf30 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -5,13 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -18,7 +18,7 @@ import { } from 'vue'; import MkButton from './MkButton.vue'; import { i18n } from '@/i18n.js'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ form: { modifiedCount: { value: number; @@ -26,7 +26,10 @@ const props = defineProps<{ discard: () => void; save: () => void; }; -}>(); + canSaving?: boolean; +}>(), { + canSaving: true, +}); diff --git a/packages/frontend/src/components/MkPagingButtons.vue b/packages/frontend/src/components/MkPagingButtons.vue new file mode 100644 index 000000000000..fe59efd83a2e --- /dev/null +++ b/packages/frontend/src/components/MkPagingButtons.vue @@ -0,0 +1,124 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index e70ac7ff1a00..1b2b3e48ba33 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + ({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }}) @@ -42,6 +42,8 @@ const props = defineProps<{ noteId: string; poll: NonNullable; readOnly?: boolean; + emojiUrls?: Record; + author?: Misskey.entities.UserLite; }>(); const remaining = ref(-1); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 0b5794d1e309..5d0716ef37e7 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -46,14 +46,14 @@ SPDX-License-Identifier: AGPL-3.0-only - + - -
{{ i18n.ts.quoteAttached }}
+ +
{{ i18n.ts.quoteAttached }}
{{ i18n.ts.recipient }}
@@ -100,12 +100,13 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts new file mode 100644 index 000000000000..411d62edf9c8 --- /dev/null +++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { http, HttpResponse } from 'msw'; +import { role } from '../../.storybook/fakes.js'; +import { commonHandlers } from '../../.storybook/mocks.js'; +import MkRoleSelectDialog from '@/components/MkRoleSelectDialog.vue'; + +const roles = [ + role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), + role({ displayOrder: 2 }, '2'), role({ displayOrder: 2 }, '2'), role({ displayOrder: 3 }, '3'), role({ displayOrder: 3 }, '3'), + role({ displayOrder: 4 }, '4'), role({ displayOrder: 5 }, '5'), role({ displayOrder: 6 }, '6'), role({ displayOrder: 7 }, '7'), + role({ displayOrder: 999, name: 'privateRole', isPublic: false }, '999'), +]; + +export const Default = { + render(args) { + return { + components: { + MkRoleSelectDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + initialRoleIds: undefined, + infoMessage: undefined, + title: undefined, + publicOnly: true, + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/admin/roles/list', ({ params }) => { + return HttpResponse.json(roles); + }), + ], + }, + }, + decorators: [() => ({ + template: '
', + })], +} satisfies StoryObj; + +export const InitialIds = { + ...Default, + args: { + ...Default.args, + initialRoleIds: [roles[0].id, roles[1].id, roles[4].id, roles[6].id, roles[8].id, roles[10].id], + }, +} satisfies StoryObj; + +export const InfoMessage = { + ...Default, + args: { + ...Default.args, + infoMessage: 'This is a message.', + }, +} satisfies StoryObj; + +export const Title = { + ...Default, + args: { + ...Default.args, + title: 'Select roles', + }, +} satisfies StoryObj; + +export const Full = { + ...Default, + args: { + ...Default.args, + initialRoleIds: roles.map(it => it.id), + infoMessage: InfoMessage.args.infoMessage, + title: Title.args.title, + }, +} satisfies StoryObj; + +export const FullWithPrivate = { + ...Default, + args: { + ...Default.args, + initialRoleIds: roles.map(it => it.id), + infoMessage: InfoMessage.args.infoMessage, + title: Title.args.title, + publicOnly: false, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue new file mode 100644 index 000000000000..67a7a3f75256 --- /dev/null +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -0,0 +1,200 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue index 34c22abc3181..e98ac9cfd2d2 100644 --- a/packages/frontend/src/components/MkSignin.input.vue +++ b/packages/frontend/src/components/MkSignin.input.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 9e02884b8ca1..5fb37ce8dcd1 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.poll }} - +